react-native-sherpa-onnx 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +92 -21
  3. package/SherpaOnnx.podspec +3 -0
  4. package/THIRD_PARTY_LICENSES/README.md +62 -0
  5. package/THIRD_PARTY_LICENSES/ffmpeg.txt +502 -0
  6. package/THIRD_PARTY_LICENSES/libarchive.txt +65 -0
  7. package/THIRD_PARTY_LICENSES/nvidia_omla.txt +181 -0
  8. package/THIRD_PARTY_LICENSES/onnxruntime.txt +21 -0
  9. package/THIRD_PARTY_LICENSES/opus.txt +44 -0
  10. package/THIRD_PARTY_LICENSES/sherpa-onnx.txt +201 -0
  11. package/THIRD_PARTY_LICENSES/shine.txt +482 -0
  12. package/THIRD_PARTY_LICENSES/zstd.txt +30 -0
  13. package/android/build.gradle +7 -3
  14. package/android/prebuilt-download.gradle +344 -152
  15. package/android/prebuilt-versions.gradle +1 -1
  16. package/android/src/main/assets/model_licenses/asr-models-license-status.csv +409 -0
  17. package/android/src/main/assets/model_licenses/qnn-asr-models-license-status.csv +695 -0
  18. package/android/src/main/assets/model_licenses/tts-models-license-status.csv +596 -0
  19. package/android/src/main/cpp/CMakeLists.txt +28 -10
  20. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +2 -2
  21. package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +268 -2
  22. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +37 -6
  23. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect.h +9 -1
  24. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.cpp +7 -0
  25. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.cpp +18 -2
  26. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +40 -10
  27. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +99 -0
  28. package/android/src/main/java/com/sherpaonnx/SherpaOnnxOnlineSttHelper.kt +4 -1
  29. package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +127 -97
  30. package/ios/Resources/model_licenses/asr-models-license-status.csv +409 -0
  31. package/ios/Resources/model_licenses/qnn-asr-models-license-status.csv +695 -0
  32. package/ios/Resources/model_licenses/tts-models-license-status.csv +596 -0
  33. package/ios/SherpaOnnx+OnlineSTT.mm +2 -0
  34. package/ios/SherpaOnnx+PcmLiveStream.mm +2 -29
  35. package/ios/SherpaOnnx+TTS.mm +179 -20
  36. package/ios/SherpaOnnx.mm +54 -0
  37. package/ios/SherpaOnnxAudioConvert.h +10 -0
  38. package/ios/SherpaOnnxAudioConvert.mm +257 -1
  39. package/ios/archive/sherpa-onnx-archive-helper.h +3 -0
  40. package/ios/archive/sherpa-onnx-archive-helper.mm +39 -6
  41. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +49 -6
  42. package/ios/model_detect/sherpa-onnx-model-detect.h +9 -1
  43. package/ios/model_detect/sherpa-onnx-validate-tts.mm +18 -2
  44. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.h +1 -0
  45. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.mm +4 -0
  46. package/ios/tts/sherpa-onnx-tts-wrapper.h +37 -0
  47. package/ios/tts/sherpa-onnx-tts-wrapper.mm +158 -3
  48. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  49. package/lib/module/audio/index.js +8 -0
  50. package/lib/module/audio/index.js.map +1 -1
  51. package/lib/module/download/ModelDownloadManager.js +10 -929
  52. package/lib/module/download/ModelDownloadManager.js.map +1 -1
  53. package/lib/module/download/activeModelOperations.js +26 -0
  54. package/lib/module/download/activeModelOperations.js.map +1 -0
  55. package/lib/module/download/background-downloader-types.js +2 -0
  56. package/lib/module/download/background-downloader-types.js.map +1 -0
  57. package/lib/module/download/bulkPurge.js +72 -0
  58. package/lib/module/download/bulkPurge.js.map +1 -0
  59. package/lib/module/download/checksumPrompt.js +19 -0
  60. package/lib/module/download/checksumPrompt.js.map +1 -0
  61. package/lib/module/download/constants.js +7 -0
  62. package/lib/module/download/constants.js.map +1 -0
  63. package/lib/module/download/downloadEvents.js +35 -0
  64. package/lib/module/download/downloadEvents.js.map +1 -0
  65. package/lib/module/download/downloadTask.js +438 -0
  66. package/lib/module/download/downloadTask.js.map +1 -0
  67. package/lib/module/download/ensureModel.js +89 -0
  68. package/lib/module/download/ensureModel.js.map +1 -0
  69. package/lib/module/download/index.js +4 -4
  70. package/lib/module/download/index.js.map +1 -1
  71. package/lib/module/download/localModels.js +151 -0
  72. package/lib/module/download/localModels.js.map +1 -0
  73. package/lib/module/download/modelExtraction.js +174 -0
  74. package/lib/module/download/modelExtraction.js.map +1 -0
  75. package/lib/module/download/paths.js +98 -0
  76. package/lib/module/download/paths.js.map +1 -0
  77. package/lib/module/download/postDownloadProcessing.js +206 -0
  78. package/lib/module/download/postDownloadProcessing.js.map +1 -0
  79. package/lib/module/download/protectedModelKeys.js +31 -0
  80. package/lib/module/download/protectedModelKeys.js.map +1 -0
  81. package/lib/module/download/registry.js +268 -0
  82. package/lib/module/download/registry.js.map +1 -0
  83. package/lib/module/download/retry.js +59 -0
  84. package/lib/module/download/retry.js.map +1 -0
  85. package/lib/module/download/types.js +17 -0
  86. package/lib/module/download/types.js.map +1 -0
  87. package/lib/module/download/validation.js +101 -5
  88. package/lib/module/download/validation.js.map +1 -1
  89. package/lib/module/{download → extraction}/extractTarBz2.js +3 -1
  90. package/lib/module/extraction/extractTarBz2.js.map +1 -0
  91. package/lib/module/{download → extraction}/extractTarZst.js +3 -1
  92. package/lib/module/extraction/extractTarZst.js.map +1 -0
  93. package/lib/module/extraction/index.js +3 -4
  94. package/lib/module/extraction/index.js.map +1 -1
  95. package/lib/module/index.js +1 -1
  96. package/lib/module/index.js.map +1 -1
  97. package/lib/module/licenses.js +63 -0
  98. package/lib/module/licenses.js.map +1 -0
  99. package/lib/module/stt/index.js +16 -2
  100. package/lib/module/stt/index.js.map +1 -1
  101. package/lib/module/stt/streaming.js +2 -0
  102. package/lib/module/stt/streaming.js.map +1 -1
  103. package/lib/module/stt/streamingTypes.js.map +1 -1
  104. package/lib/module/stt/types.js.map +1 -1
  105. package/lib/module/tts/index.js +21 -3
  106. package/lib/module/tts/index.js.map +1 -1
  107. package/lib/module/tts/streaming.js +5 -1
  108. package/lib/module/tts/streaming.js.map +1 -1
  109. package/lib/module/tts/types.js +4 -1
  110. package/lib/module/tts/types.js.map +1 -1
  111. package/lib/module/utils.js +16 -1
  112. package/lib/module/utils.js.map +1 -1
  113. package/lib/typescript/src/NativeSherpaOnnx.d.ts +34 -6
  114. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  115. package/lib/typescript/src/audio/index.d.ts +10 -0
  116. package/lib/typescript/src/audio/index.d.ts.map +1 -1
  117. package/lib/typescript/src/download/ModelDownloadManager.d.ts +11 -108
  118. package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -1
  119. package/lib/typescript/src/download/activeModelOperations.d.ts +6 -0
  120. package/lib/typescript/src/download/activeModelOperations.d.ts.map +1 -0
  121. package/lib/typescript/src/download/background-downloader-types.d.ts +64 -0
  122. package/lib/typescript/src/download/background-downloader-types.d.ts.map +1 -0
  123. package/lib/typescript/src/download/bulkPurge.d.ts +14 -0
  124. package/lib/typescript/src/download/bulkPurge.d.ts.map +1 -0
  125. package/lib/typescript/src/download/checksumPrompt.d.ts +3 -0
  126. package/lib/typescript/src/download/checksumPrompt.d.ts.map +1 -0
  127. package/lib/typescript/src/download/constants.d.ts +5 -0
  128. package/lib/typescript/src/download/constants.d.ts.map +1 -0
  129. package/lib/typescript/src/download/downloadEvents.d.ts +6 -0
  130. package/lib/typescript/src/download/downloadEvents.d.ts.map +1 -0
  131. package/lib/typescript/src/download/downloadTask.d.ts +30 -0
  132. package/lib/typescript/src/download/downloadTask.d.ts.map +1 -0
  133. package/lib/typescript/src/download/ensureModel.d.ts +26 -0
  134. package/lib/typescript/src/download/ensureModel.d.ts.map +1 -0
  135. package/lib/typescript/src/download/index.d.ts +7 -7
  136. package/lib/typescript/src/download/index.d.ts.map +1 -1
  137. package/lib/typescript/src/download/localModels.d.ts +15 -0
  138. package/lib/typescript/src/download/localModels.d.ts.map +1 -0
  139. package/lib/typescript/src/download/modelExtraction.d.ts +36 -0
  140. package/lib/typescript/src/download/modelExtraction.d.ts.map +1 -0
  141. package/lib/typescript/src/download/paths.d.ts +28 -0
  142. package/lib/typescript/src/download/paths.d.ts.map +1 -0
  143. package/lib/typescript/src/download/postDownloadProcessing.d.ts +19 -0
  144. package/lib/typescript/src/download/postDownloadProcessing.d.ts.map +1 -0
  145. package/lib/typescript/src/download/protectedModelKeys.d.ts +6 -0
  146. package/lib/typescript/src/download/protectedModelKeys.d.ts.map +1 -0
  147. package/lib/typescript/src/download/registry.d.ts +14 -0
  148. package/lib/typescript/src/download/registry.d.ts.map +1 -0
  149. package/lib/typescript/src/download/retry.d.ts +15 -0
  150. package/lib/typescript/src/download/retry.d.ts.map +1 -0
  151. package/lib/typescript/src/download/types.d.ts +96 -0
  152. package/lib/typescript/src/download/types.d.ts.map +1 -0
  153. package/lib/typescript/src/download/validation.d.ts +19 -0
  154. package/lib/typescript/src/download/validation.d.ts.map +1 -1
  155. package/lib/typescript/src/extraction/extractTarBz2.d.ts.map +1 -0
  156. package/lib/typescript/src/extraction/extractTarZst.d.ts.map +1 -0
  157. package/lib/typescript/src/index.d.ts +1 -0
  158. package/lib/typescript/src/index.d.ts.map +1 -1
  159. package/lib/typescript/src/licenses.d.ts +10 -0
  160. package/lib/typescript/src/licenses.d.ts.map +1 -0
  161. package/lib/typescript/src/stt/index.d.ts +4 -1
  162. package/lib/typescript/src/stt/index.d.ts.map +1 -1
  163. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  164. package/lib/typescript/src/stt/streamingTypes.d.ts +5 -0
  165. package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
  166. package/lib/typescript/src/stt/types.d.ts +3 -1
  167. package/lib/typescript/src/stt/types.d.ts.map +1 -1
  168. package/lib/typescript/src/tts/index.d.ts +4 -2
  169. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  170. package/lib/typescript/src/tts/streaming.d.ts.map +1 -1
  171. package/lib/typescript/src/tts/types.d.ts +12 -6
  172. package/lib/typescript/src/tts/types.d.ts.map +1 -1
  173. package/lib/typescript/src/utils.d.ts +5 -0
  174. package/lib/typescript/src/utils.d.ts.map +1 -1
  175. package/package.json +6 -1
  176. package/scripts/{check-model-csvs.sh → ci/check-model-csvs.sh} +9 -2
  177. package/scripts/ci/collect_all_sherpa_model_streams.sh +101 -0
  178. package/scripts/ci/collect_one_sherpa_release_stream.sh +189 -0
  179. package/scripts/ci/sherpa_asr_model_release_streams.json +21 -0
  180. package/scripts/ci/sherpa_tts_model_release_streams.json +13 -0
  181. package/scripts/ci/update_model_license_csv.sh +765 -0
  182. package/scripts/setup-ios-framework.sh +14 -11
  183. package/scripts/update_commercial_use.js +73 -0
  184. package/src/NativeSherpaOnnx.ts +37 -6
  185. package/src/audio/index.ts +20 -0
  186. package/src/download/ModelDownloadManager.ts +57 -1343
  187. package/src/download/activeModelOperations.ts +38 -0
  188. package/src/download/background-downloader-types.ts +73 -0
  189. package/src/download/bulkPurge.ts +102 -0
  190. package/src/download/checksumPrompt.ts +25 -0
  191. package/src/download/constants.ts +5 -0
  192. package/src/download/downloadEvents.ts +55 -0
  193. package/src/download/downloadTask.ts +565 -0
  194. package/src/download/ensureModel.ts +124 -0
  195. package/src/download/index.ts +21 -4
  196. package/src/download/localModels.ts +234 -0
  197. package/src/download/modelExtraction.ts +244 -0
  198. package/src/download/paths.ts +134 -0
  199. package/src/download/postDownloadProcessing.ts +292 -0
  200. package/src/download/protectedModelKeys.ts +30 -0
  201. package/src/download/registry.ts +405 -0
  202. package/src/download/retry.ts +76 -0
  203. package/src/download/types.ts +120 -0
  204. package/src/download/validation.ts +114 -8
  205. package/src/{download → extraction}/extractTarBz2.ts +3 -1
  206. package/src/{download → extraction}/extractTarZst.ts +3 -1
  207. package/src/extraction/index.ts +3 -7
  208. package/src/index.tsx +1 -0
  209. package/src/licenses.ts +100 -0
  210. package/src/stt/index.ts +20 -2
  211. package/src/stt/streaming.ts +3 -0
  212. package/src/stt/streamingTypes.ts +5 -0
  213. package/src/stt/types.ts +3 -1
  214. package/src/tts/index.ts +33 -2
  215. package/src/tts/streaming.ts +12 -0
  216. package/src/tts/types.ts +15 -5
  217. package/src/utils.ts +22 -1
  218. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  219. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
  220. package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +0 -301
  221. package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +0 -187
  222. package/lib/module/download/extractTarBz2.js.map +0 -1
  223. package/lib/module/download/extractTarZst.js.map +0 -1
  224. package/lib/typescript/src/download/extractTarBz2.d.ts.map +0 -1
  225. package/lib/typescript/src/download/extractTarZst.d.ts.map +0 -1
  226. package/scripts/check-qnn-support.sh +0 -78
  227. /package/lib/typescript/src/{download → extraction}/extractTarBz2.d.ts +0 -0
  228. /package/lib/typescript/src/{download → extraction}/extractTarZst.d.ts +0 -0
@@ -0,0 +1,292 @@
1
+ import {
2
+ exists,
3
+ mkdir,
4
+ writeFile,
5
+ stat,
6
+ unlink,
7
+ } from '@dr.pogodin/react-native-fs';
8
+ import type {
9
+ ModelCategory,
10
+ ModelMetaBase,
11
+ ModelManifest,
12
+ ChecksumIssue,
13
+ DownloadProgress,
14
+ } from './types';
15
+ import {
16
+ getReadyMarkerPath,
17
+ getManifestPath,
18
+ getExtractionStatePath,
19
+ } from './paths';
20
+ import {
21
+ validateChecksum,
22
+ validateExtractedFiles,
23
+ resolveActualModelDir,
24
+ } from './validation';
25
+ import { extractTarBz2 } from '../extraction/extractTarBz2';
26
+ import { promptChecksumFallback } from './checksumPrompt';
27
+ import { emitDownloadProgress, emitModelsListUpdated } from './downloadEvents';
28
+ import type { DownloadResult } from './types';
29
+ import {
30
+ registerActivePostProcess,
31
+ unregisterActivePostProcess,
32
+ } from './activeModelOperations';
33
+
34
+ export type RunPostDownloadProcessingOptions = {
35
+ category: ModelCategory;
36
+ id: string;
37
+ model: ModelMetaBase;
38
+ downloadPath: string;
39
+ modelDir: string;
40
+ isArchive: boolean;
41
+ statePath: string;
42
+ signal?: AbortSignal;
43
+ onChecksumIssue?: (issue: ChecksumIssue) => Promise<boolean>;
44
+ deleteArchiveAfterExtract?: boolean;
45
+ onProgress?: (progress: DownloadProgress) => void;
46
+ /** Called to get current list of downloaded models for emitModelsListUpdated. */
47
+ getDownloadedList: () => Promise<ModelMetaBase[]>;
48
+ };
49
+
50
+ export async function runPostDownloadProcessing(
51
+ options: RunPostDownloadProcessingOptions
52
+ ): Promise<DownloadResult> {
53
+ const {
54
+ category,
55
+ id,
56
+ model,
57
+ downloadPath,
58
+ modelDir,
59
+ isArchive,
60
+ statePath,
61
+ signal,
62
+ onChecksumIssue,
63
+ deleteArchiveAfterExtract,
64
+ onProgress,
65
+ getDownloadedList,
66
+ } = options;
67
+
68
+ registerActivePostProcess(category, id);
69
+ try {
70
+ return await runPostDownloadProcessingBody({
71
+ category,
72
+ id,
73
+ model,
74
+ downloadPath,
75
+ modelDir,
76
+ isArchive,
77
+ statePath,
78
+ signal,
79
+ onChecksumIssue,
80
+ deleteArchiveAfterExtract,
81
+ onProgress,
82
+ getDownloadedList,
83
+ });
84
+ } finally {
85
+ unregisterActivePostProcess(category, id);
86
+ }
87
+ }
88
+
89
+ async function runPostDownloadProcessingBody(
90
+ options: RunPostDownloadProcessingOptions
91
+ ): Promise<DownloadResult> {
92
+ const {
93
+ category,
94
+ id,
95
+ model,
96
+ downloadPath,
97
+ modelDir,
98
+ isArchive,
99
+ statePath,
100
+ signal,
101
+ onChecksumIssue,
102
+ deleteArchiveAfterExtract,
103
+ onProgress,
104
+ getDownloadedList,
105
+ } = options;
106
+
107
+ const isAborted = () => Boolean(signal?.aborted);
108
+ const abortError = new Error('Download aborted');
109
+ abortError.name = 'AbortError';
110
+
111
+ if (signal?.aborted) throw abortError;
112
+
113
+ let extractResult: { sha256?: string } | null = null;
114
+ let extractedTotalBytes = 0;
115
+
116
+ if (isArchive) {
117
+ try {
118
+ const archiveStat = await stat(downloadPath);
119
+ if (model.bytes > 0 && archiveStat.size < model.bytes) {
120
+ console.warn(
121
+ `[Download] Archive truncated for ${category}:${id}: ${archiveStat.size}/${model.bytes} bytes. Deleting for re-download.`
122
+ );
123
+ await unlink(downloadPath);
124
+ throw new Error(
125
+ `Archive file is truncated (${archiveStat.size}/${model.bytes} bytes). Please retry the download.`
126
+ );
127
+ }
128
+ } catch (statErr) {
129
+ if (statErr instanceof Error && statErr.message.includes('truncated'))
130
+ throw statErr;
131
+ }
132
+ await mkdir(modelDir);
133
+ const extractionStatePath = getExtractionStatePath(category, id);
134
+ try {
135
+ await writeFile(
136
+ extractionStatePath,
137
+ JSON.stringify({
138
+ modelId: id,
139
+ category,
140
+ phase: 'extracting' as const,
141
+ startedAt: new Date().toISOString(),
142
+ archivePath: downloadPath,
143
+ modelDir,
144
+ model,
145
+ }),
146
+ 'utf8'
147
+ );
148
+ } catch {
149
+ // non-fatal; resume after crash may not be possible for this run
150
+ }
151
+ extractResult = await extractTarBz2(
152
+ downloadPath,
153
+ modelDir,
154
+ true,
155
+ (evt) => {
156
+ if (isAborted()) return;
157
+ if (evt.totalBytes > 0) extractedTotalBytes = evt.totalBytes;
158
+ const progress: DownloadProgress = {
159
+ bytesDownloaded: evt.bytes,
160
+ totalBytes: evt.totalBytes,
161
+ percent: evt.percent,
162
+ phase: 'extracting',
163
+ };
164
+ onProgress?.(progress);
165
+ emitDownloadProgress(category, id, progress);
166
+ },
167
+ signal
168
+ );
169
+ }
170
+
171
+ if (model.sha256) {
172
+ const expectedSha = model.sha256.toLowerCase();
173
+ let issue: ChecksumIssue | null = null;
174
+ if (isArchive) {
175
+ const nativeSha = extractResult?.sha256?.toLowerCase();
176
+ if (!nativeSha) {
177
+ issue = {
178
+ category,
179
+ id,
180
+ archivePath: downloadPath,
181
+ expected: model.sha256,
182
+ message: 'Native SHA-256 not available after extraction.',
183
+ reason: 'CHECKSUM_FAILED',
184
+ };
185
+ } else if (nativeSha !== expectedSha) {
186
+ issue = {
187
+ category,
188
+ id,
189
+ archivePath: downloadPath,
190
+ expected: model.sha256,
191
+ message: `Checksum mismatch: expected ${model.sha256}, got ${extractResult?.sha256}`,
192
+ reason: 'CHECKSUM_MISMATCH',
193
+ };
194
+ }
195
+ } else {
196
+ const checksumResult = await validateChecksum(downloadPath, expectedSha);
197
+ if (!checksumResult.success) {
198
+ issue = {
199
+ category,
200
+ id,
201
+ archivePath: downloadPath,
202
+ expected: model.sha256,
203
+ message: checksumResult.message ?? 'Checksum validation failed.',
204
+ reason:
205
+ checksumResult.error === 'CHECKSUM_MISMATCH'
206
+ ? 'CHECKSUM_MISMATCH'
207
+ : 'CHECKSUM_FAILED',
208
+ };
209
+ }
210
+ }
211
+ if (issue) {
212
+ const keepFile = onChecksumIssue
213
+ ? await onChecksumIssue(issue)
214
+ : await promptChecksumFallback(issue);
215
+ if (!keepFile) {
216
+ if (await exists(modelDir)) await unlink(modelDir);
217
+ if (await exists(downloadPath)) await unlink(downloadPath);
218
+ throw new Error(`Checksum validation failed: ${issue.message}`);
219
+ }
220
+ }
221
+ }
222
+
223
+ if (signal?.aborted) throw abortError;
224
+
225
+ const filesValidation = await validateExtractedFiles(modelDir, category);
226
+ if (!filesValidation.success) {
227
+ await unlink(modelDir);
228
+ throw new Error(
229
+ `Extracted files validation failed: ${filesValidation.message}`
230
+ );
231
+ }
232
+
233
+ await writeFile(getReadyMarkerPath(category, id), 'ready', 'utf8');
234
+ const now = new Date().toISOString();
235
+ let sizeOnDisk: number | undefined;
236
+ if (isArchive && extractedTotalBytes > 0) {
237
+ sizeOnDisk = extractedTotalBytes;
238
+ } else if (!isArchive) {
239
+ try {
240
+ const s = await stat(downloadPath);
241
+ sizeOnDisk = s.size;
242
+ } catch {
243
+ // ignore
244
+ }
245
+ }
246
+ await writeFile(
247
+ getManifestPath(category, id),
248
+ JSON.stringify({
249
+ downloadedAt: now,
250
+ lastUsed: now,
251
+ model,
252
+ sizeOnDisk,
253
+ } as ModelManifest),
254
+ 'utf8'
255
+ );
256
+
257
+ try {
258
+ if (await exists(statePath)) await unlink(statePath);
259
+ } catch {
260
+ // non-fatal
261
+ }
262
+ if (isArchive) {
263
+ try {
264
+ const extractionStatePath = getExtractionStatePath(category, id);
265
+ if (
266
+ extractionStatePath !== statePath &&
267
+ (await exists(extractionStatePath))
268
+ ) {
269
+ await unlink(extractionStatePath);
270
+ }
271
+ } catch {
272
+ // non-fatal
273
+ }
274
+ }
275
+
276
+ if (isArchive && deleteArchiveAfterExtract !== false) {
277
+ try {
278
+ if (await exists(downloadPath)) await unlink(downloadPath);
279
+ } catch (err) {
280
+ console.warn(
281
+ `[Download] Failed to delete archive after extraction for ${category}:${id}:`,
282
+ err instanceof Error ? err.message : String(err)
283
+ );
284
+ }
285
+ }
286
+
287
+ const list = await getDownloadedList();
288
+ emitModelsListUpdated(category, list);
289
+
290
+ const resolvedPath = await resolveActualModelDir(modelDir);
291
+ return { modelId: id, localPath: resolvedPath };
292
+ }
@@ -0,0 +1,30 @@
1
+ import { getExistingDownloadTasks } from '@kesha-antonov/react-native-background-downloader';
2
+ import { getActivePostProcessKeys } from './activeModelOperations';
3
+ import { getActiveDownloadTaskKeys } from './downloadTask';
4
+
5
+ /**
6
+ * Model keys (`category:modelId`) that must not be removed by bulk delete: active JS download tasks,
7
+ * native background-downloader tasks, and models in post-download processing (extraction / validation).
8
+ */
9
+ export async function getProtectedModelKeysForBulkDelete(): Promise<
10
+ ReadonlySet<string>
11
+ > {
12
+ const set = new Set<string>();
13
+ for (const k of getActiveDownloadTaskKeys()) {
14
+ set.add(k);
15
+ }
16
+ for (const k of getActivePostProcessKeys()) {
17
+ set.add(k);
18
+ }
19
+ try {
20
+ const existing = await getExistingDownloadTasks();
21
+ for (const t of existing) {
22
+ if (t.id && typeof t.id === 'string') {
23
+ set.add(t.id);
24
+ }
25
+ }
26
+ } catch {
27
+ // ignore — still return JS/post-process protection
28
+ }
29
+ return set;
30
+ }