react-native-sherpa-onnx 0.3.5 → 0.3.6

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 (43) hide show
  1. package/README.md +2 -1
  2. package/android/prebuilt-download.gradle +1 -1
  3. package/android/prebuilt-versions.gradle +1 -1
  4. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +306 -6
  5. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +33 -4
  6. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +266 -7
  7. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +100 -0
  8. package/android/src/main/java/com/sherpaonnx/SherpaOnnxAssetHelper.kt +51 -6
  9. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +60 -0
  10. package/ios/SherpaOnnx.mm +54 -1
  11. package/ios/archive/sherpa-onnx-archive-helper.h +7 -0
  12. package/ios/archive/sherpa-onnx-archive-helper.mm +18 -0
  13. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  14. package/lib/module/download/extractTarZst.js +52 -0
  15. package/lib/module/download/extractTarZst.js.map +1 -0
  16. package/lib/module/download/index.js +1 -0
  17. package/lib/module/download/index.js.map +1 -1
  18. package/lib/module/extraction/index.js +191 -0
  19. package/lib/module/extraction/index.js.map +1 -0
  20. package/lib/module/extraction/types.js +2 -0
  21. package/lib/module/extraction/types.js.map +1 -0
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/typescript/src/NativeSherpaOnnx.d.ts +39 -0
  25. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  26. package/lib/typescript/src/download/extractTarZst.d.ts +14 -0
  27. package/lib/typescript/src/download/extractTarZst.d.ts.map +1 -0
  28. package/lib/typescript/src/download/index.d.ts +2 -0
  29. package/lib/typescript/src/download/index.d.ts.map +1 -1
  30. package/lib/typescript/src/extraction/index.d.ts +50 -0
  31. package/lib/typescript/src/extraction/index.d.ts.map +1 -0
  32. package/lib/typescript/src/extraction/types.d.ts +60 -0
  33. package/lib/typescript/src/extraction/types.d.ts.map +1 -0
  34. package/lib/typescript/src/index.d.ts.map +1 -1
  35. package/package.json +6 -1
  36. package/src/NativeSherpaOnnx.ts +56 -0
  37. package/src/download/extractTarZst.ts +77 -0
  38. package/src/download/index.ts +2 -0
  39. package/src/extraction/index.ts +273 -0
  40. package/src/extraction/types.ts +63 -0
  41. package/src/index.tsx +1 -0
  42. package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
  43. package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
@@ -49,11 +49,9 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
49
49
  j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
50
50
  }
51
51
 
52
- // Get Promise.resolve and reject methods
52
+ // Get Promise.resolve method
53
53
  jclass promise_class = env->GetObjectClass(j_promise);
54
54
  jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
55
- jmethodID reject_method = env->GetMethodID(promise_class, "reject",
56
- "(Ljava/lang/String;Ljava/lang/String;)V");
57
55
 
58
56
  // Get WritableMap from Arguments
59
57
  jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
@@ -123,15 +121,12 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
123
121
  env->CallVoidMethod(result_map, put_string_method,
124
122
  env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
125
123
  }
126
- env->CallVoidMethod(j_promise, resolve_method, result_map);
127
124
  } else {
128
125
  __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
129
126
  env->CallVoidMethod(result_map, put_string_method,
130
127
  env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
131
- env->CallVoidMethod(j_promise, reject_method,
132
- env->NewStringUTF("ARCHIVE_ERROR"),
133
- env->NewStringUTF(error_msg.c_str()));
134
128
  }
129
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
135
130
 
136
131
  // Clean up global reference
137
132
  if (j_progress_callback_global != nullptr) {
@@ -144,6 +139,270 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
144
139
  env->DeleteLocalRef(writeable_map_class);
145
140
  }
146
141
 
142
+ extern "C" JNIEXPORT void JNICALL
143
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZst(
144
+ JNIEnv* env,
145
+ jobject /* jthis */,
146
+ jstring j_source_path,
147
+ jstring j_target_path,
148
+ jboolean j_force,
149
+ jobject j_progress_callback,
150
+ jobject j_promise) {
151
+ const char* source_path = env->GetStringUTFChars(j_source_path, nullptr);
152
+ const char* target_path = env->GetStringUTFChars(j_target_path, nullptr);
153
+ std::string source_str(source_path);
154
+ std::string target_str(target_path);
155
+ env->ReleaseStringUTFChars(j_source_path, source_path);
156
+ env->ReleaseStringUTFChars(j_target_path, target_path);
157
+
158
+ jmethodID on_progress_method = nullptr;
159
+ jobject j_progress_callback_global = nullptr;
160
+ if (j_progress_callback != nullptr) {
161
+ jclass callback_class = env->GetObjectClass(j_progress_callback);
162
+ on_progress_method = env->GetMethodID(callback_class, "invoke", "(JJD)V");
163
+ env->DeleteLocalRef(callback_class);
164
+ j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
165
+ }
166
+
167
+ jclass promise_class = env->GetObjectClass(j_promise);
168
+ jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
169
+
170
+ jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
171
+ jmethodID create_map_method = env->GetStaticMethodID(
172
+ arguments_class, "createMap", "()Lcom/facebook/react/bridge/WritableMap;");
173
+ jobject result_map = env->CallStaticObjectMethod(arguments_class, create_map_method);
174
+
175
+ jclass writeable_map_class = env->FindClass("com/facebook/react/bridge/WritableMap");
176
+ jmethodID put_boolean_method = env->GetMethodID(
177
+ writeable_map_class, "putBoolean", "(Ljava/lang/String;Z)V");
178
+ jmethodID put_string_method = env->GetMethodID(
179
+ writeable_map_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
180
+
181
+ auto on_progress = [j_progress_callback_global, on_progress_method](
182
+ long long bytes_extracted, long long total_bytes, double percent) {
183
+ if (j_progress_callback_global != nullptr && on_progress_method != nullptr) {
184
+ JNIEnv* callback_env = nullptr;
185
+ bool should_detach = false;
186
+ if (g_vm->GetEnv(reinterpret_cast<void**>(&callback_env), JNI_VERSION_1_6) == JNI_EDETACHED) {
187
+ if (g_vm->AttachCurrentThread(&callback_env, nullptr) == JNI_OK) {
188
+ should_detach = true;
189
+ } else {
190
+ return;
191
+ }
192
+ }
193
+ if (callback_env != nullptr) {
194
+ callback_env->CallVoidMethod(j_progress_callback_global, on_progress_method,
195
+ bytes_extracted, total_bytes, percent);
196
+ if (callback_env->ExceptionCheck()) {
197
+ callback_env->ExceptionClear();
198
+ }
199
+ if (should_detach) {
200
+ g_vm->DetachCurrentThread();
201
+ }
202
+ }
203
+ }
204
+ };
205
+
206
+ std::string error_msg;
207
+ std::string sha256;
208
+ bool success = ArchiveHelper::ExtractTarZst(
209
+ source_str,
210
+ target_str,
211
+ j_force == JNI_TRUE,
212
+ on_progress,
213
+ &error_msg,
214
+ &sha256);
215
+
216
+ env->CallVoidMethod(result_map, put_boolean_method,
217
+ env->NewStringUTF("success"), success ? JNI_TRUE : JNI_FALSE);
218
+
219
+ if (success) {
220
+ env->CallVoidMethod(result_map, put_string_method,
221
+ env->NewStringUTF("path"), env->NewStringUTF(target_str.c_str()));
222
+ if (!sha256.empty()) {
223
+ env->CallVoidMethod(result_map, put_string_method,
224
+ env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
225
+ }
226
+ } else {
227
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
228
+ env->CallVoidMethod(result_map, put_string_method,
229
+ env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
230
+ }
231
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
232
+
233
+ if (j_progress_callback_global != nullptr) {
234
+ env->DeleteGlobalRef(j_progress_callback_global);
235
+ }
236
+
237
+ env->DeleteLocalRef(result_map);
238
+ env->DeleteLocalRef(promise_class);
239
+ env->DeleteLocalRef(arguments_class);
240
+ env->DeleteLocalRef(writeable_map_class);
241
+ }
242
+
243
+ namespace {
244
+ struct InputStreamReadContext {
245
+ JNIEnv* env = nullptr;
246
+ jobject stream_global = nullptr;
247
+ jmethodID read_method = nullptr;
248
+ jbyteArray byte_array = nullptr;
249
+ const size_t buffer_size = 64 * 1024;
250
+ };
251
+
252
+ static std::ptrdiff_t JniStreamRead(void* buf, size_t len, void* user_data) {
253
+ auto* ctx = static_cast<InputStreamReadContext*>(user_data);
254
+ if (!ctx || !ctx->env || !ctx->stream_global || !ctx->read_method || !ctx->byte_array) {
255
+ return -1;
256
+ }
257
+ size_t to_read = (len < ctx->buffer_size) ? len : ctx->buffer_size;
258
+ jint n = ctx->env->CallIntMethod(ctx->stream_global, ctx->read_method, ctx->byte_array);
259
+ if (ctx->env->ExceptionCheck()) {
260
+ ctx->env->ExceptionClear();
261
+ return -1;
262
+ }
263
+ if (n <= 0) return 0;
264
+ ctx->env->GetByteArrayRegion(ctx->byte_array, 0, n, static_cast<jbyte*>(buf));
265
+ return static_cast<std::ptrdiff_t>(n);
266
+ }
267
+ } // namespace
268
+
269
+ extern "C" JNIEXPORT void JNICALL
270
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZstFromStream(
271
+ JNIEnv* env,
272
+ jobject /* jthis */,
273
+ jobject j_input_stream,
274
+ jstring j_target_path,
275
+ jboolean j_force,
276
+ jobject j_progress_callback,
277
+ jobject j_promise) {
278
+ const char* target_path = env->GetStringUTFChars(j_target_path, nullptr);
279
+ std::string target_str(target_path);
280
+ env->ReleaseStringUTFChars(j_target_path, target_path);
281
+
282
+ jobject stream_global = env->NewGlobalRef(j_input_stream);
283
+ jclass stream_class = env->GetObjectClass(j_input_stream);
284
+ jmethodID read_method = env->GetMethodID(stream_class, "read", "([B)I");
285
+ env->DeleteLocalRef(stream_class);
286
+ if (!read_method) {
287
+ env->DeleteGlobalRef(stream_global);
288
+ jclass promise_class = env->GetObjectClass(j_promise);
289
+ jmethodID reject_method = env->GetMethodID(promise_class, "reject", "(Ljava/lang/String;Ljava/lang/String;)V");
290
+ env->CallVoidMethod(j_promise, reject_method,
291
+ env->NewStringUTF("ARCHIVE_ERROR"),
292
+ env->NewStringUTF("InputStream.read([B)I not found"));
293
+ env->DeleteLocalRef(promise_class);
294
+ return;
295
+ }
296
+
297
+ jbyteArray byte_array = env->NewByteArray(static_cast<jsize>(64 * 1024));
298
+ if (!byte_array) {
299
+ env->DeleteGlobalRef(stream_global);
300
+ return;
301
+ }
302
+
303
+ InputStreamReadContext read_ctx;
304
+ read_ctx.env = env;
305
+ read_ctx.stream_global = stream_global;
306
+ read_ctx.read_method = read_method;
307
+ read_ctx.byte_array = byte_array;
308
+
309
+ jmethodID on_progress_method = nullptr;
310
+ jobject j_progress_callback_global = nullptr;
311
+ if (j_progress_callback != nullptr) {
312
+ jclass callback_class = env->GetObjectClass(j_progress_callback);
313
+ on_progress_method = env->GetMethodID(callback_class, "invoke", "(JJD)V");
314
+ env->DeleteLocalRef(callback_class);
315
+ j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
316
+ }
317
+
318
+ jclass promise_class = env->GetObjectClass(j_promise);
319
+ jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
320
+
321
+ jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
322
+ jmethodID create_map_method = env->GetStaticMethodID(arguments_class, "createMap", "()Lcom/facebook/react/bridge/WritableMap;");
323
+ jobject result_map = env->CallStaticObjectMethod(arguments_class, create_map_method);
324
+
325
+ jclass writeable_map_class = env->FindClass("com/facebook/react/bridge/WritableMap");
326
+ jmethodID put_boolean_method = env->GetMethodID(writeable_map_class, "putBoolean", "(Ljava/lang/String;Z)V");
327
+ jmethodID put_string_method = env->GetMethodID(writeable_map_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
328
+
329
+ auto on_progress = [j_progress_callback_global, on_progress_method](
330
+ long long bytes_extracted, long long total_bytes, double percent) {
331
+ if (j_progress_callback_global != nullptr && on_progress_method != nullptr) {
332
+ JNIEnv* callback_env = nullptr;
333
+ bool should_detach = false;
334
+ if (g_vm->GetEnv(reinterpret_cast<void**>(&callback_env), JNI_VERSION_1_6) == JNI_EDETACHED) {
335
+ if (g_vm->AttachCurrentThread(&callback_env, nullptr) == JNI_OK) {
336
+ should_detach = true;
337
+ } else {
338
+ return;
339
+ }
340
+ }
341
+ if (callback_env != nullptr) {
342
+ callback_env->CallVoidMethod(j_progress_callback_global, on_progress_method,
343
+ bytes_extracted, total_bytes, percent);
344
+ if (callback_env->ExceptionCheck()) {
345
+ callback_env->ExceptionClear();
346
+ }
347
+ if (should_detach) {
348
+ g_vm->DetachCurrentThread();
349
+ }
350
+ }
351
+ }
352
+ };
353
+
354
+ std::string error_msg;
355
+ std::string sha256;
356
+ bool success = ArchiveHelper::ExtractFromStream(
357
+ &JniStreamRead,
358
+ &read_ctx,
359
+ target_str,
360
+ j_force == JNI_TRUE,
361
+ on_progress,
362
+ &error_msg,
363
+ &sha256);
364
+
365
+ env->CallVoidMethod(result_map, put_boolean_method,
366
+ env->NewStringUTF("success"), success ? JNI_TRUE : JNI_FALSE);
367
+
368
+ if (success) {
369
+ env->CallVoidMethod(result_map, put_string_method,
370
+ env->NewStringUTF("path"), env->NewStringUTF(target_str.c_str()));
371
+ if (!sha256.empty()) {
372
+ env->CallVoidMethod(result_map, put_string_method,
373
+ env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
374
+ }
375
+ } else {
376
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
377
+ env->CallVoidMethod(result_map, put_string_method,
378
+ env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
379
+ }
380
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
381
+
382
+ env->DeleteGlobalRef(stream_global);
383
+ env->DeleteLocalRef(byte_array);
384
+ if (j_progress_callback_global != nullptr) {
385
+ env->DeleteGlobalRef(j_progress_callback_global);
386
+ }
387
+ env->DeleteLocalRef(result_map);
388
+ env->DeleteLocalRef(promise_class);
389
+ env->DeleteLocalRef(arguments_class);
390
+ env->DeleteLocalRef(writeable_map_class);
391
+ }
392
+
393
+ extern "C" JNIEXPORT void JNICALL
394
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2FromStream(
395
+ JNIEnv* env,
396
+ jobject jthis,
397
+ jobject j_input_stream,
398
+ jstring j_target_path,
399
+ jboolean j_force,
400
+ jobject j_progress_callback,
401
+ jobject j_promise) {
402
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZstFromStream(
403
+ env, jthis, j_input_stream, j_target_path, j_force, j_progress_callback, j_promise);
404
+ }
405
+
147
406
  extern "C" JNIEXPORT void JNICALL
148
407
  Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeCancelExtract(JNIEnv* /* env */, jobject /* jthis */) {
149
408
  ArchiveHelper::Cancel();
@@ -1,5 +1,7 @@
1
1
  package com.sherpaonnx
2
2
 
3
+ import android.content.Context
4
+ import android.util.Log
3
5
  import com.facebook.react.bridge.Arguments
4
6
  import com.facebook.react.bridge.Promise
5
7
  import java.util.concurrent.ExecutorService
@@ -31,6 +33,11 @@ class SherpaOnnxArchiveHelper {
31
33
  nativeCancelExtract()
32
34
  }
33
35
 
36
+ fun cancelExtractTarZst() {
37
+ cancelRequested.set(true)
38
+ nativeCancelExtract()
39
+ }
40
+
34
41
  fun extractTarBz2(
35
42
  sourcePath: String,
36
43
  targetPath: String,
@@ -71,6 +78,83 @@ class SherpaOnnxArchiveHelper {
71
78
  }
72
79
  }
73
80
 
81
+ fun extractTarZst(
82
+ sourcePath: String,
83
+ targetPath: String,
84
+ force: Boolean,
85
+ promise: Promise,
86
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
87
+ ) {
88
+ val promiseSettled = AtomicBoolean(false)
89
+ fun resolveOnce(success: Boolean, reason: String? = null) {
90
+ if (!promiseSettled.compareAndSet(false, true)) return
91
+ val result = Arguments.createMap()
92
+ result.putBoolean("success", success)
93
+ if (reason != null) result.putString("reason", reason)
94
+ promise.resolve(result)
95
+ }
96
+
97
+ try {
98
+ cancelRequested.set(false)
99
+ val progressCallback = object : Any() {
100
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
101
+ onProgress(bytesExtracted, totalBytes, percent)
102
+ }
103
+ }
104
+ extractExecutor.execute {
105
+ try {
106
+ nativeExtractTarZst(sourcePath, targetPath, force, progressCallback, promise)
107
+ } catch (e: Exception) {
108
+ resolveOnce(false, "Archive extraction error: ${e.message}")
109
+ }
110
+ }
111
+ } catch (e: Exception) {
112
+ resolveOnce(false, "Archive extraction error: ${e.message}")
113
+ }
114
+ }
115
+
116
+ fun extractTarZstFromAsset(
117
+ context: Context,
118
+ assetPath: String,
119
+ targetPath: String,
120
+ force: Boolean,
121
+ promise: Promise,
122
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
123
+ ) {
124
+ if (BuildConfig.DEBUG) {
125
+ Log.i("SherpaOnnx", "extractTarZstFromAsset assetPath=$assetPath targetPath=$targetPath")
126
+ }
127
+ cancelRequested.set(false)
128
+ val progressCallback = object : Any() {
129
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
130
+ onProgress(bytesExtracted, totalBytes, percent)
131
+ }
132
+ }
133
+ extractExecutor.execute {
134
+ try {
135
+ context.assets.open(assetPath).use { stream ->
136
+ nativeExtractTarZstFromStream(stream, targetPath, force, progressCallback, promise)
137
+ }
138
+ } catch (e: Exception) {
139
+ val result = Arguments.createMap()
140
+ result.putBoolean("success", false)
141
+ result.putString("reason", e.message ?: "Failed to open asset")
142
+ promise.resolve(result)
143
+ }
144
+ }
145
+ }
146
+
147
+ fun extractTarBz2FromAsset(
148
+ context: Context,
149
+ assetPath: String,
150
+ targetPath: String,
151
+ force: Boolean,
152
+ promise: Promise,
153
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
154
+ ) {
155
+ extractTarZstFromAsset(context, assetPath, targetPath, force, promise, onProgress)
156
+ }
157
+
74
158
  fun computeFileSha256(filePath: String, promise: Promise) {
75
159
  nativeComputeFileSha256(filePath, promise)
76
160
  }
@@ -84,6 +168,22 @@ class SherpaOnnxArchiveHelper {
84
168
  promise: Promise
85
169
  )
86
170
 
171
+ private external fun nativeExtractTarZst(
172
+ sourcePath: String,
173
+ targetPath: String,
174
+ force: Boolean,
175
+ progressCallback: Any?,
176
+ promise: Promise
177
+ )
178
+
179
+ private external fun nativeExtractTarZstFromStream(
180
+ inputStream: java.io.InputStream,
181
+ targetPath: String,
182
+ force: Boolean,
183
+ progressCallback: Any?,
184
+ promise: Promise
185
+ )
186
+
87
187
  private external fun nativeCancelExtract()
88
188
 
89
189
  private external fun nativeComputeFileSha256(
@@ -82,10 +82,12 @@ internal class SherpaOnnxAssetHelper(
82
82
  try {
83
83
  val baseDir = File(path)
84
84
  if (!baseDir.exists()) {
85
- throw IllegalArgumentException("Path does not exist: $path")
85
+ promise.resolve(Arguments.createArray())
86
+ return
86
87
  }
87
88
  if (!baseDir.isDirectory) {
88
- throw IllegalArgumentException("Path is not a directory: $path")
89
+ promise.resolve(Arguments.createArray())
90
+ return
89
91
  }
90
92
 
91
93
  val folders = mutableListOf<String>()
@@ -134,11 +136,18 @@ internal class SherpaOnnxAssetHelper(
134
136
  try {
135
137
  Log.i(logTag, "getAssetPackPath: packName=$packName")
136
138
  val assetPackManager = AssetPackManagerFactory.getInstance(context)
137
- val location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
139
+ var location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
138
140
  if (location == null) {
139
- Log.i(logTag, "getAssetPackPath: location is null for pack '$packName'")
140
- promise.resolve(null)
141
- return
141
+ val allLocations = assetPackManager.getPackLocations()
142
+ location = allLocations?.get(packName)
143
+ if (allLocations != null) {
144
+ Log.i(logTag, "getAssetPackPath: getPackLocation was null, getPackLocations keys=${allLocations.keys}")
145
+ }
146
+ if (location == null) {
147
+ Log.i(logTag, "getAssetPackPath: location is null for pack '$packName'")
148
+ promise.resolve(null)
149
+ return
150
+ }
142
151
  }
143
152
  Log.i(logTag, "getAssetPackPath: storageMethod=${location.packStorageMethod()}, " +
144
153
  "assetsPath=${location.assetsPath()}, path=${location.path()}")
@@ -170,6 +179,42 @@ internal class SherpaOnnxAssetHelper(
170
179
  }
171
180
  }
172
181
 
182
+ /**
183
+ * Lists asset paths of .tar.zst and .tar.bz2 archives in a PAD pack when stored as APK_ASSETS.
184
+ * Returns empty array when pack is null or STORAGE_FILES (caller uses path + readDir in that case).
185
+ * APK_ASSETS: pack content is merged into the app asset root; canonical path is "models"
186
+ * (pack layout src/main/assets/models/). Same for Play Store and bundletool install-time delivery.
187
+ */
188
+ fun listBundledArchiveAssetPaths(packName: String, promise: Promise) {
189
+ try {
190
+ val assetPackManager = AssetPackManagerFactory.getInstance(context)
191
+ var location: AssetPackLocation? = assetPackManager.getPackLocation(packName)
192
+ if (location == null) {
193
+ location = assetPackManager.getPackLocations()?.get(packName)
194
+ }
195
+ if (location == null) {
196
+ promise.resolve(Arguments.createArray())
197
+ return
198
+ }
199
+ if (location.packStorageMethod() != AssetPackStorageMethod.STORAGE_FILES) {
200
+ val assetPrefix = "models"
201
+ val names = context.assets.list(assetPrefix) ?: emptyArray()
202
+ val archives = names.filter { it.endsWith(".tar.zst") || it.endsWith(".tar.bz2") }
203
+ val result = Arguments.createArray()
204
+ for (name in archives) {
205
+ result.pushString("$assetPrefix/$name")
206
+ }
207
+ Log.i(logTag, "listBundledArchiveAssetPaths: packName=$packName prefix=$assetPrefix count=${result.size()}")
208
+ promise.resolve(result)
209
+ } else {
210
+ promise.resolve(Arguments.createArray())
211
+ }
212
+ } catch (e: Exception) {
213
+ Log.w(logTag, "listBundledArchiveAssetPaths failed: ${e.message}")
214
+ promise.resolve(Arguments.createArray())
215
+ }
216
+ }
217
+
173
218
  private fun resolveAssetPath(assetPath: String): String {
174
219
  Log.i(logTag, "resolveAssetPath: assetPath=$assetPath")
175
220
  val assetManager = context.assets
@@ -303,6 +303,17 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
303
303
  promise.resolve(null)
304
304
  }
305
305
 
306
+ override fun extractTarZst(sourcePath: String, targetPath: String, force: Boolean, promise: Promise) {
307
+ archiveHelper.extractTarZst(sourcePath, targetPath, force, promise) { bytes, total, percent ->
308
+ emitExtractTarZstProgress(sourcePath, bytes, total, percent)
309
+ }
310
+ }
311
+
312
+ override fun cancelExtractTarZst(promise: Promise) {
313
+ archiveHelper.cancelExtractTarZst()
314
+ promise.resolve(null)
315
+ }
316
+
306
317
  override fun computeFileSha256(filePath: String, promise: Promise) {
307
318
  archiveHelper.computeFileSha256(filePath, promise)
308
319
  }
@@ -318,6 +329,17 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
318
329
  eventEmitter.emit("extractTarBz2Progress", payload)
319
330
  }
320
331
 
332
+ private fun emitExtractTarZstProgress(sourcePath: String, bytes: Long, totalBytes: Long, percent: Double) {
333
+ val eventEmitter = reactApplicationContext
334
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
335
+ val payload = Arguments.createMap()
336
+ payload.putString("sourcePath", sourcePath)
337
+ payload.putDouble("bytes", bytes.toDouble())
338
+ payload.putDouble("totalBytes", totalBytes.toDouble())
339
+ payload.putDouble("percent", percent)
340
+ eventEmitter.emit("extractTarZstProgress", payload)
341
+ }
342
+
321
343
  /**
322
344
  * Resolve asset path - copy from assets to internal storage if needed
323
345
  * Preserves the directory structure from assets (e.g., test_wavs/ stays as test_wavs/)
@@ -997,6 +1019,44 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
997
1019
  assetHelper.getAssetPackPath(packName, promise)
998
1020
  }
999
1021
 
1022
+ override fun listBundledArchiveAssetPaths(packName: String, promise: Promise) {
1023
+ assetHelper.listBundledArchiveAssetPaths(packName, promise)
1024
+ }
1025
+
1026
+ override fun extractTarZstFromAsset(
1027
+ assetPath: String,
1028
+ targetPath: String,
1029
+ force: Boolean,
1030
+ promise: Promise
1031
+ ) {
1032
+ archiveHelper.extractTarZstFromAsset(
1033
+ reactApplicationContext,
1034
+ assetPath,
1035
+ targetPath,
1036
+ force,
1037
+ promise
1038
+ ) { bytes, total, percent ->
1039
+ emitExtractTarZstProgress(assetPath, bytes, total, percent)
1040
+ }
1041
+ }
1042
+
1043
+ override fun extractTarBz2FromAsset(
1044
+ assetPath: String,
1045
+ targetPath: String,
1046
+ force: Boolean,
1047
+ promise: Promise
1048
+ ) {
1049
+ archiveHelper.extractTarBz2FromAsset(
1050
+ reactApplicationContext,
1051
+ assetPath,
1052
+ targetPath,
1053
+ force,
1054
+ promise
1055
+ ) { bytes, total, percent ->
1056
+ emitExtractProgress(assetPath, bytes, total, percent)
1057
+ }
1058
+ }
1059
+
1000
1060
  companion object {
1001
1061
  const val NAME = "SherpaOnnx"
1002
1062
 
package/ios/SherpaOnnx.mm CHANGED
@@ -38,7 +38,7 @@
38
38
 
39
39
  - (NSArray<NSString *> *)supportedEvents
40
40
  {
41
- return @[ @"ttsStreamChunk", @"ttsStreamEnd", @"ttsStreamError", @"extractTarBz2Progress", @"pcmLiveStreamData", @"pcmLiveStreamError" ];
41
+ return @[ @"ttsStreamChunk", @"ttsStreamEnd", @"ttsStreamError", @"extractTarBz2Progress", @"extractTarZstProgress", @"pcmLiveStreamData", @"pcmLiveStreamError" ];
42
42
  }
43
43
 
44
44
  - (void)resolveModelPath:(JS::NativeSherpaOnnx::SpecResolveModelPathConfig &)config
@@ -162,6 +162,33 @@
162
162
  resolve(nil);
163
163
  }
164
164
 
165
+ - (void)extractTarZst:(NSString *)sourcePath
166
+ targetPath:(NSString *)targetPath
167
+ force:(BOOL)force
168
+ resolve:(RCTPromiseResolveBlock)resolve
169
+ reject:(RCTPromiseRejectBlock)reject
170
+ {
171
+ SherpaOnnxArchiveHelper *helper = [SherpaOnnxArchiveHelper new];
172
+ NSDictionary *result = [helper extractTarZst:sourcePath
173
+ targetPath:targetPath
174
+ force:force
175
+ progress:^(long long bytes, long long totalBytes, double percent) {
176
+ [self sendEventWithName:@"extractTarZstProgress"
177
+ body:@{ @"sourcePath": sourcePath,
178
+ @"bytes": @(bytes),
179
+ @"totalBytes": @(totalBytes),
180
+ @"percent": @(percent) }];
181
+ }];
182
+ resolve(result);
183
+ }
184
+
185
+ - (void)cancelExtractTarZst:(RCTPromiseResolveBlock)resolve
186
+ reject:(RCTPromiseRejectBlock)reject
187
+ {
188
+ [SherpaOnnxArchiveHelper cancelExtractTarZst];
189
+ resolve(nil);
190
+ }
191
+
165
192
  - (void)computeFileSha256:(NSString *)filePath
166
193
  resolve:(RCTPromiseResolveBlock)resolve
167
194
  reject:(RCTPromiseRejectBlock)reject
@@ -184,6 +211,32 @@
184
211
  resolve([NSNull null]);
185
212
  }
186
213
 
214
+ - (void)listBundledArchiveAssetPaths:(NSString *)packName
215
+ resolve:(RCTPromiseResolveBlock)resolve
216
+ reject:(RCTPromiseRejectBlock)reject
217
+ {
218
+ // PAD APK_ASSETS listing is Android-only.
219
+ resolve(@[]);
220
+ }
221
+
222
+ - (void)extractTarZstFromAsset:(NSString *)assetPath
223
+ targetPath:(NSString *)targetPath
224
+ force:(NSNumber *)force
225
+ resolve:(RCTPromiseResolveBlock)resolve
226
+ reject:(RCTPromiseRejectBlock)reject
227
+ {
228
+ resolve(@{ @"success": @NO, @"reason": @"Not supported on iOS; use path-based extraction." });
229
+ }
230
+
231
+ - (void)extractTarBz2FromAsset:(NSString *)assetPath
232
+ targetPath:(NSString *)targetPath
233
+ force:(NSNumber *)force
234
+ resolve:(RCTPromiseResolveBlock)resolve
235
+ reject:(RCTPromiseRejectBlock)reject
236
+ {
237
+ resolve(@{ @"success": @NO, @"reason": @"Not supported on iOS; use path-based extraction." });
238
+ }
239
+
187
240
  - (void)convertAudioToFormat:(NSString *)inputPath
188
241
  outputPath:(NSString *)outputPath
189
242
  format:(NSString *)format
@@ -11,11 +11,18 @@ typedef void (^SherpaOnnxArchiveProgressBlock)(long long bytes, long long totalB
11
11
  force:(BOOL)force
12
12
  progress:(nullable SherpaOnnxArchiveProgressBlock)progress;
13
13
 
14
+ - (NSDictionary *)extractTarZst:(NSString *)sourcePath
15
+ targetPath:(NSString *)targetPath
16
+ force:(BOOL)force
17
+ progress:(nullable SherpaOnnxArchiveProgressBlock)progress;
18
+
14
19
  - (nullable NSString *)computeFileSha256:(NSString *)filePath
15
20
  error:(NSError * _Nullable * _Nullable)error;
16
21
 
17
22
  + (void)cancelExtractTarBz2;
18
23
 
24
+ + (void)cancelExtractTarZst;
25
+
19
26
  @end
20
27
 
21
28
  NS_ASSUME_NONNULL_END