react-native-sherpa-onnx 0.2.0 → 0.3.0

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 (175) hide show
  1. package/README.md +232 -236
  2. package/SherpaOnnx.podspec +68 -64
  3. package/android/build.gradle +182 -192
  4. package/android/codegen.gradle +57 -0
  5. package/android/prebuilt-download.gradle +428 -0
  6. package/android/prebuilt-versions.gradle +43 -0
  7. package/android/proguard-rules.pro +10 -0
  8. package/android/src/main/assets/testModels/add_mul_add.onnx +28 -0
  9. package/android/src/main/assets/testModels/nnapi_internal_uint8_support.onnx +0 -0
  10. package/android/src/main/assets/testModels/qnn_multi_ctx_embed.onnx +0 -0
  11. package/android/src/main/cpp/CMakeLists.txt +166 -129
  12. package/android/src/main/cpp/CMakePresets.json +54 -0
  13. package/android/src/main/cpp/crypto/sha256.cpp +174 -0
  14. package/android/src/main/cpp/crypto/sha256.h +16 -0
  15. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +404 -0
  16. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +56 -0
  17. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +181 -0
  18. package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +888 -0
  19. package/{ios → android/src/main/cpp/jni/model_detect}/sherpa-onnx-common.h +18 -18
  20. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.cpp +86 -0
  21. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.h +20 -0
  22. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.cpp +423 -0
  23. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.h +55 -0
  24. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-stt.cpp +399 -0
  25. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +238 -0
  26. package/{ios → android/src/main/cpp/jni/model_detect}/sherpa-onnx-model-detect.h +122 -89
  27. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.cpp +99 -0
  28. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.h +16 -0
  29. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.cpp +78 -0
  30. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.h +16 -0
  31. package/android/src/main/cpp/jni/module/sherpa-onnx-module-jni.cpp +190 -0
  32. package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +301 -0
  33. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +94 -0
  34. package/android/src/main/java/com/sherpaonnx/{SherpaOnnxCoreHelper.kt → SherpaOnnxAssetHelper.kt} +350 -236
  35. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +791 -483
  36. package/android/src/main/java/com/sherpaonnx/SherpaOnnxSttHelper.kt +699 -109
  37. package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +1123 -668
  38. package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +187 -0
  39. package/ios/SherpaOnnx+Assets.h +11 -0
  40. package/ios/SherpaOnnx+Assets.mm +325 -0
  41. package/ios/SherpaOnnx+STT.mm +455 -118
  42. package/ios/SherpaOnnx+TTS.mm +1101 -712
  43. package/ios/SherpaOnnx.h +17 -6
  44. package/ios/SherpaOnnx.mm +206 -311
  45. package/ios/SherpaOnnx.xcconfig +19 -19
  46. package/ios/SherpaOnnxCoreMLHelper.swift +24 -0
  47. package/ios/archive/sherpa-onnx-archive-helper.h +21 -0
  48. package/ios/archive/sherpa-onnx-archive-helper.mm +296 -0
  49. package/ios/libarchive_darwin_config.h +153 -0
  50. package/{android/src/main/cpp/jni → ios/model_detect}/sherpa-onnx-common.h +18 -18
  51. package/ios/model_detect/sherpa-onnx-model-detect-helper.h +49 -0
  52. package/ios/model_detect/sherpa-onnx-model-detect-helper.mm +210 -0
  53. package/ios/model_detect/sherpa-onnx-model-detect-stt.mm +344 -0
  54. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +201 -0
  55. package/{android/src/main/cpp/jni → ios/model_detect}/sherpa-onnx-model-detect.h +117 -89
  56. package/ios/scripts/patch-libarchive-includes.sh +61 -0
  57. package/ios/scripts/setup-ios-libarchive.sh +98 -0
  58. package/ios/stt/sherpa-onnx-stt-wrapper.h +129 -0
  59. package/ios/stt/sherpa-onnx-stt-wrapper.mm +523 -0
  60. package/ios/{sherpa-onnx-tts-wrapper.h → tts/sherpa-onnx-tts-wrapper.h} +90 -85
  61. package/ios/{sherpa-onnx-tts-wrapper.mm → tts/sherpa-onnx-tts-wrapper.mm} +376 -345
  62. package/lib/module/NativeSherpaOnnx.js +3 -0
  63. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  64. package/lib/module/audio/index.js +22 -0
  65. package/lib/module/audio/index.js.map +1 -0
  66. package/lib/module/diarization/index.js +1 -1
  67. package/lib/module/diarization/index.js.map +1 -1
  68. package/lib/module/download/ModelDownloadManager.js +918 -0
  69. package/lib/module/download/ModelDownloadManager.js.map +1 -0
  70. package/lib/module/download/extractTarBz2.js +53 -0
  71. package/lib/module/download/extractTarBz2.js.map +1 -0
  72. package/lib/module/download/index.js +6 -0
  73. package/lib/module/download/index.js.map +1 -0
  74. package/lib/module/download/validation.js +178 -0
  75. package/lib/module/download/validation.js.map +1 -0
  76. package/lib/module/enhancement/index.js +1 -1
  77. package/lib/module/enhancement/index.js.map +1 -1
  78. package/lib/module/index.js +41 -3
  79. package/lib/module/index.js.map +1 -1
  80. package/lib/module/separation/index.js +1 -1
  81. package/lib/module/separation/index.js.map +1 -1
  82. package/lib/module/stt/index.js +127 -60
  83. package/lib/module/stt/index.js.map +1 -1
  84. package/lib/module/stt/sttModelLanguages.js +512 -0
  85. package/lib/module/stt/sttModelLanguages.js.map +1 -0
  86. package/lib/module/stt/types.js +53 -1
  87. package/lib/module/stt/types.js.map +1 -1
  88. package/lib/module/tts/index.js +216 -289
  89. package/lib/module/tts/index.js.map +1 -1
  90. package/lib/module/tts/types.js +86 -1
  91. package/lib/module/tts/types.js.map +1 -1
  92. package/lib/module/types.js.map +1 -1
  93. package/lib/module/utils.js +86 -73
  94. package/lib/module/utils.js.map +1 -1
  95. package/lib/module/vad/index.js +1 -1
  96. package/lib/module/vad/index.js.map +1 -1
  97. package/lib/typescript/src/NativeSherpaOnnx.d.ts +192 -38
  98. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  99. package/lib/typescript/src/audio/index.d.ts +13 -0
  100. package/lib/typescript/src/audio/index.d.ts.map +1 -0
  101. package/lib/typescript/src/diarization/index.d.ts +3 -2
  102. package/lib/typescript/src/diarization/index.d.ts.map +1 -1
  103. package/lib/typescript/src/download/ModelDownloadManager.d.ts +108 -0
  104. package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -0
  105. package/lib/typescript/src/download/extractTarBz2.d.ts +14 -0
  106. package/lib/typescript/src/download/extractTarBz2.d.ts.map +1 -0
  107. package/lib/typescript/src/download/index.d.ts +7 -0
  108. package/lib/typescript/src/download/index.d.ts.map +1 -0
  109. package/lib/typescript/src/download/validation.d.ts +57 -0
  110. package/lib/typescript/src/download/validation.d.ts.map +1 -0
  111. package/lib/typescript/src/enhancement/index.d.ts +3 -2
  112. package/lib/typescript/src/enhancement/index.d.ts.map +1 -1
  113. package/lib/typescript/src/index.d.ts +26 -2
  114. package/lib/typescript/src/index.d.ts.map +1 -1
  115. package/lib/typescript/src/separation/index.d.ts +3 -2
  116. package/lib/typescript/src/separation/index.d.ts.map +1 -1
  117. package/lib/typescript/src/stt/index.d.ts +31 -43
  118. package/lib/typescript/src/stt/index.d.ts.map +1 -1
  119. package/lib/typescript/src/stt/sttModelLanguages.d.ts +52 -0
  120. package/lib/typescript/src/stt/sttModelLanguages.d.ts.map +1 -0
  121. package/lib/typescript/src/stt/types.d.ts +196 -9
  122. package/lib/typescript/src/stt/types.d.ts.map +1 -1
  123. package/lib/typescript/src/tts/index.d.ts +25 -211
  124. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  125. package/lib/typescript/src/tts/types.d.ts +148 -25
  126. package/lib/typescript/src/tts/types.d.ts.map +1 -1
  127. package/lib/typescript/src/types.d.ts +0 -32
  128. package/lib/typescript/src/types.d.ts.map +1 -1
  129. package/lib/typescript/src/utils.d.ts +28 -13
  130. package/lib/typescript/src/utils.d.ts.map +1 -1
  131. package/lib/typescript/src/vad/index.d.ts +3 -2
  132. package/lib/typescript/src/vad/index.d.ts.map +1 -1
  133. package/package.json +250 -222
  134. package/scripts/check-qnn-support.sh +78 -0
  135. package/scripts/setup-ios-framework.sh +379 -282
  136. package/src/NativeSherpaOnnx.ts +474 -251
  137. package/src/audio/index.ts +32 -0
  138. package/src/diarization/index.ts +4 -2
  139. package/src/download/ModelDownloadManager.ts +1325 -0
  140. package/src/download/extractTarBz2.ts +78 -0
  141. package/src/download/index.ts +43 -0
  142. package/src/download/validation.ts +279 -0
  143. package/src/enhancement/index.ts +4 -2
  144. package/src/index.tsx +78 -27
  145. package/src/separation/index.ts +4 -2
  146. package/src/stt/index.ts +249 -89
  147. package/src/stt/sttModelLanguages.ts +237 -0
  148. package/src/stt/types.ts +263 -9
  149. package/src/tts/index.ts +470 -458
  150. package/src/tts/types.ts +373 -218
  151. package/src/types.ts +0 -44
  152. package/src/utils.ts +145 -131
  153. package/src/vad/index.ts +4 -2
  154. package/third_party/ffmpeg_prebuilt/ANDROID_RELEASE_TAG +1 -0
  155. package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -0
  156. package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -0
  157. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -0
  158. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -0
  159. package/android/src/main/cpp/include/sherpa-onnx/c-api/c-api.h +0 -1918
  160. package/android/src/main/cpp/include/sherpa-onnx/c-api/cxx-api.h +0 -841
  161. package/android/src/main/cpp/jni/sherpa-onnx-model-detect.cpp +0 -541
  162. package/android/src/main/cpp/jni/sherpa-onnx-stt-jni.cpp +0 -336
  163. package/android/src/main/cpp/jni/sherpa-onnx-stt-wrapper.cpp +0 -222
  164. package/android/src/main/cpp/jni/sherpa-onnx-stt-wrapper.h +0 -68
  165. package/android/src/main/cpp/jni/sherpa-onnx-tts-jni.cpp +0 -823
  166. package/android/src/main/cpp/jni/sherpa-onnx-tts-wrapper.cpp +0 -387
  167. package/android/src/main/cpp/jni/sherpa-onnx-tts-wrapper.h +0 -147
  168. package/ios/Frameworks/sherpa_onnx.xcframework.zip +0 -0
  169. package/ios/include/sherpa-onnx/c-api/c-api.h +0 -1918
  170. package/ios/include/sherpa-onnx/c-api/cxx-api.h +0 -841
  171. package/ios/sherpa-onnx-model-detect.mm +0 -441
  172. package/ios/sherpa-onnx-stt-wrapper.h +0 -48
  173. package/ios/sherpa-onnx-stt-wrapper.mm +0 -201
  174. package/scripts/copy-headers.js +0 -184
  175. package/scripts/setup-assets.js +0 -323
@@ -0,0 +1,404 @@
1
+ /**
2
+ * sherpa-onnx-archive-helper.cpp
3
+ *
4
+ * Purpose: Extracts .tar.bz2 archives to a target directory and computes file SHA-256. Used by
5
+ * sherpa-onnx-archive-jni.cpp for model download and verification on Android.
6
+ */
7
+ #include "sherpa-onnx-archive-helper.h"
8
+
9
+ #include <archive.h>
10
+ #include <archive_entry.h>
11
+ #include <array>
12
+ #include <atomic>
13
+ #include <cerrno>
14
+ #include <cstring>
15
+ #include <filesystem>
16
+ #include <cstdio>
17
+ #include <android/log.h>
18
+ #include "crypto/sha256.h"
19
+
20
+ // TAG is defined but may not be used depending on logging configuration
21
+
22
+ // Global cancellation flag
23
+ std::atomic<bool> ArchiveHelper::cancel_requested_(false);
24
+
25
+ namespace {
26
+ struct ArchiveReadContext {
27
+ FILE* file = nullptr;
28
+ std::array<unsigned char, 64 * 1024> buffer{};
29
+ Sha256Context sha_ctx{};
30
+ long long bytes_read = 0;
31
+ };
32
+
33
+ static la_ssize_t ArchiveReadCallback(struct archive* archive, void* client_data, const void** buff) {
34
+ auto* ctx = static_cast<ArchiveReadContext*>(client_data);
35
+ if (!ctx || !ctx->file) {
36
+ archive_set_error(archive, EINVAL, "Invalid read context");
37
+ return -1;
38
+ }
39
+
40
+ size_t bytes = fread(ctx->buffer.data(), 1, ctx->buffer.size(), ctx->file);
41
+ if (bytes > 0) {
42
+ sha256_update(&ctx->sha_ctx, ctx->buffer.data(), bytes);
43
+ ctx->bytes_read += static_cast<long long>(bytes);
44
+ *buff = ctx->buffer.data();
45
+ return static_cast<la_ssize_t>(bytes);
46
+ }
47
+
48
+ if (feof(ctx->file)) {
49
+ return 0;
50
+ }
51
+
52
+ archive_set_error(archive, errno, "Read error");
53
+ return -1;
54
+ }
55
+
56
+ static int ArchiveCloseCallback(struct archive* /* archive */, void* client_data) {
57
+ (void)client_data;
58
+ return ARCHIVE_OK;
59
+ }
60
+
61
+ static void DrainRemainingAndClose(ArchiveReadContext* ctx) {
62
+ if (!ctx || !ctx->file) {
63
+ return;
64
+ }
65
+
66
+ size_t bytes = 0;
67
+ while ((bytes = fread(ctx->buffer.data(), 1, ctx->buffer.size(), ctx->file)) > 0) {
68
+ sha256_update(&ctx->sha_ctx, ctx->buffer.data(), bytes);
69
+ ctx->bytes_read += static_cast<long long>(bytes);
70
+ }
71
+
72
+ fclose(ctx->file);
73
+ ctx->file = nullptr;
74
+ }
75
+
76
+ static std::string ToHex(const unsigned char* data, size_t size) {
77
+ static const char* kHex = "0123456789abcdef";
78
+ std::string out;
79
+ out.reserve(size * 2);
80
+ for (size_t i = 0; i < size; ++i) {
81
+ unsigned char value = data[i];
82
+ out.push_back(kHex[value >> 4]);
83
+ out.push_back(kHex[value & 0x0F]);
84
+ }
85
+ return out;
86
+ }
87
+ } // namespace
88
+
89
+ bool ArchiveHelper::IsCancelled() {
90
+ return cancel_requested_.load();
91
+ }
92
+
93
+ void ArchiveHelper::Cancel() {
94
+ cancel_requested_.store(true);
95
+ }
96
+
97
+ bool ArchiveHelper::ExtractTarBz2(
98
+ const std::string& source_path,
99
+ const std::string& target_path,
100
+ bool force,
101
+ std::function<void(long long, long long, double)> on_progress,
102
+ std::string* out_error,
103
+ std::string* out_sha256) {
104
+ cancel_requested_.store(false);
105
+
106
+ // Validate source file exists
107
+ if (!std::filesystem::exists(source_path)) {
108
+ if (out_error) *out_error = "Source file does not exist";
109
+ return false;
110
+ }
111
+
112
+ // Check target directory
113
+ if (std::filesystem::exists(target_path)) {
114
+ if (force) {
115
+ std::error_code ec;
116
+ std::filesystem::remove_all(target_path, ec);
117
+ if (ec) {
118
+ if (out_error) *out_error = "Failed to remove target directory: " + ec.message();
119
+ return false;
120
+ }
121
+ } else {
122
+ if (out_error) *out_error = "Target path already exists";
123
+ return false;
124
+ }
125
+ }
126
+
127
+ // Create target directory
128
+ std::error_code ec;
129
+ std::filesystem::create_directories(target_path, ec);
130
+ if (ec) {
131
+ if (out_error) *out_error = "Failed to create target directory: " + ec.message();
132
+ return false;
133
+ }
134
+
135
+ // Get canonical target path for security check
136
+ std::string canonical_target = std::filesystem::canonical(target_path).string();
137
+ if (canonical_target.back() != '/') {
138
+ canonical_target += '/';
139
+ }
140
+
141
+ // Get total file size
142
+ long long total_bytes = 0;
143
+ try {
144
+ total_bytes = std::filesystem::file_size(source_path);
145
+ } catch (const std::exception& e) {
146
+ if (out_error) *out_error = std::string("Failed to get file size: ") + e.what();
147
+ return false;
148
+ }
149
+
150
+ // Open archive for reading with hashing reader
151
+ struct archive* archive = archive_read_new();
152
+ if (!archive) {
153
+ if (out_error) *out_error = "Failed to create archive reader";
154
+ return false;
155
+ }
156
+
157
+ // Configure archive to support tar and bzip2
158
+ archive_read_support_format_tar(archive);
159
+ archive_read_support_filter_bzip2(archive);
160
+ archive_read_support_filter_gzip(archive); // Also support gzip for compatibility
161
+ archive_read_support_filter_xz(archive); // And xz
162
+
163
+ ArchiveReadContext read_ctx;
164
+ read_ctx.file = fopen(source_path.c_str(), "rb");
165
+ if (!read_ctx.file) {
166
+ if (out_error) *out_error = std::string("Failed to open archive file: ") + std::strerror(errno);
167
+ archive_read_free(archive);
168
+ return false;
169
+ }
170
+ auto close_reader = [&read_ctx]() {
171
+ DrainRemainingAndClose(&read_ctx);
172
+ };
173
+ sha256_init(&read_ctx.sha_ctx);
174
+
175
+ if (archive_read_open(archive, &read_ctx, nullptr, ArchiveReadCallback, ArchiveCloseCallback) != ARCHIVE_OK) {
176
+ const char* err = archive_error_string(archive);
177
+ if (out_error) {
178
+ *out_error = err ? std::string("Failed to open archive: ") + err : "Failed to open archive";
179
+ }
180
+ close_reader();
181
+ archive_read_free(archive);
182
+ return false;
183
+ }
184
+
185
+ // Create disk writer
186
+ struct archive* disk = archive_write_disk_new();
187
+ if (!disk) {
188
+ if (out_error) *out_error = "Failed to create disk writer";
189
+ archive_read_free(archive);
190
+ return false;
191
+ }
192
+
193
+ archive_write_disk_set_options(disk,
194
+ ARCHIVE_EXTRACT_TIME |
195
+ ARCHIVE_EXTRACT_PERM |
196
+ ARCHIVE_EXTRACT_ACL |
197
+ ARCHIVE_EXTRACT_FFLAGS);
198
+ archive_write_disk_set_standard_lookup(disk);
199
+
200
+ // Extract entries
201
+ struct archive_entry* entry = nullptr;
202
+ int result = ARCHIVE_OK;
203
+ long long extracted_bytes = 0;
204
+ int last_percent = -1;
205
+ long long last_emit_bytes = 0;
206
+
207
+ while ((result = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
208
+ if (cancel_requested_.load()) {
209
+ if (out_error) *out_error = "Extraction cancelled";
210
+ archive_read_free(archive);
211
+ archive_write_free(disk);
212
+ close_reader();
213
+ return false;
214
+ }
215
+
216
+ // Get entry path and construct full path
217
+ const char* current_path = archive_entry_pathname(entry);
218
+ if (!current_path) {
219
+ archive_read_free(archive);
220
+ archive_write_free(disk);
221
+ close_reader();
222
+ if (out_error) *out_error = "Invalid entry path";
223
+ return false;
224
+ }
225
+
226
+ std::string entry_path(current_path);
227
+ std::string full_path = target_path;
228
+ if (full_path.back() != '/') full_path += '/';
229
+ full_path += entry_path;
230
+
231
+ // Security check: ensure path doesn't escape target directory
232
+ std::string canonical_entry;
233
+ try {
234
+ // For entries that don't exist yet, canonicalize the parent directory
235
+ std::filesystem::path p(full_path);
236
+ std::filesystem::path parent = p.parent_path();
237
+
238
+ if (std::filesystem::exists(parent)) {
239
+ canonical_entry = std::filesystem::canonical(parent).string();
240
+ } else {
241
+ // Try to canonicalize as much as possible
242
+ while (!std::filesystem::exists(parent) && parent != parent.parent_path()) {
243
+ parent = parent.parent_path();
244
+ }
245
+ if (std::filesystem::exists(parent)) {
246
+ canonical_entry = std::filesystem::canonical(parent).string();
247
+ } else {
248
+ canonical_entry = canonical_target;
249
+ }
250
+ }
251
+ canonical_entry += '/';
252
+ canonical_entry += p.filename().string();
253
+ } catch (const std::exception&) {
254
+ canonical_entry = full_path;
255
+ }
256
+
257
+ // Check if the canonical path is within target
258
+ if (canonical_entry.find(canonical_target) != 0) {
259
+ archive_read_free(archive);
260
+ archive_write_free(disk);
261
+ close_reader();
262
+ if (out_error) *out_error = "Blocked path traversal: " + entry_path;
263
+ return false;
264
+ }
265
+
266
+ // Set the pathname for extraction
267
+ archive_entry_set_pathname(entry, full_path.c_str());
268
+
269
+ // Write header
270
+ result = archive_write_header(disk, entry);
271
+ if (result != ARCHIVE_OK) {
272
+ const char* err = archive_error_string(disk);
273
+ if (out_error) {
274
+ *out_error = err ? std::string("Failed to write entry: ") + err : "Failed to write entry";
275
+ }
276
+ archive_read_free(archive);
277
+ archive_write_free(disk);
278
+ close_reader();
279
+ return false;
280
+ }
281
+
282
+ // Write data
283
+ const void* buff = nullptr;
284
+ size_t size = 0;
285
+ la_int64_t offset = 0;
286
+
287
+ while ((result = archive_read_data_block(archive, &buff, &size, &offset)) == ARCHIVE_OK) {
288
+ if (cancel_requested_.load()) {
289
+ if (out_error) *out_error = "Extraction cancelled";
290
+ archive_read_free(archive);
291
+ archive_write_free(disk);
292
+ close_reader();
293
+ return false;
294
+ }
295
+
296
+ result = archive_write_data_block(disk, buff, size, offset);
297
+ if (result != ARCHIVE_OK) {
298
+ const char* err = archive_error_string(disk);
299
+ if (out_error) {
300
+ *out_error = err ? std::string("Failed to write data: ") + err : "Failed to write data";
301
+ }
302
+ archive_read_free(archive);
303
+ archive_write_free(disk);
304
+ close_reader();
305
+ return false;
306
+ }
307
+
308
+ extracted_bytes += static_cast<long long>(size);
309
+
310
+ // Progress callback
311
+ if (on_progress) {
312
+ if (total_bytes > 0) {
313
+ // Use bytes read from source (filter -1) to align with archive file size.
314
+ long long compressed_bytes = archive_filter_bytes(archive, -1);
315
+ int percent = static_cast<int>((compressed_bytes * 100) / total_bytes);
316
+ if (percent > 100) {
317
+ percent = 100;
318
+ } else if (percent < 0) {
319
+ percent = 0;
320
+ }
321
+
322
+ if (percent != last_percent) {
323
+ last_percent = percent;
324
+ on_progress(compressed_bytes, total_bytes, static_cast<double>(percent));
325
+ }
326
+ } else if (extracted_bytes - last_emit_bytes >= 1024 * 1024) {
327
+ // If total_bytes unknown, emit every 1MB
328
+ last_emit_bytes = extracted_bytes;
329
+ on_progress(extracted_bytes, total_bytes, 0.0);
330
+ }
331
+ }
332
+ }
333
+
334
+ if (result != ARCHIVE_EOF && result != ARCHIVE_OK) {
335
+ const char* err = archive_error_string(archive);
336
+ if (out_error) {
337
+ *out_error = err ? std::string("Failed to read data: ") + err : "Failed to read data";
338
+ }
339
+ archive_read_free(archive);
340
+ archive_write_free(disk);
341
+ close_reader();
342
+ return false;
343
+ }
344
+ }
345
+
346
+ archive_read_free(archive);
347
+ archive_write_free(disk);
348
+
349
+ close_reader();
350
+
351
+ if (out_sha256) {
352
+ unsigned char digest[32];
353
+ sha256_final(&read_ctx.sha_ctx, digest);
354
+ *out_sha256 = ToHex(digest, sizeof(digest));
355
+ }
356
+
357
+ // Final progress: report uncompressed size so JS can store sizeOnDisk
358
+ if (on_progress) {
359
+ on_progress(extracted_bytes, extracted_bytes, 100.0);
360
+ }
361
+
362
+ return true;
363
+ }
364
+
365
+ bool ArchiveHelper::ComputeFileSha256(
366
+ const std::string& file_path,
367
+ std::string* out_error,
368
+ std::string* out_sha256) {
369
+ if (!std::filesystem::exists(file_path)) {
370
+ if (out_error) *out_error = "File does not exist";
371
+ return false;
372
+ }
373
+
374
+ FILE* file = fopen(file_path.c_str(), "rb");
375
+ if (!file) {
376
+ if (out_error) *out_error = std::string("Failed to open file: ") + std::strerror(errno);
377
+ return false;
378
+ }
379
+
380
+ Sha256Context ctx;
381
+ sha256_init(&ctx);
382
+ std::array<unsigned char, 64 * 1024> buffer{};
383
+
384
+ size_t bytes = 0;
385
+ while ((bytes = fread(buffer.data(), 1, buffer.size(), file)) > 0) {
386
+ sha256_update(&ctx, buffer.data(), bytes);
387
+ }
388
+
389
+ if (ferror(file)) {
390
+ if (out_error) *out_error = "Read error while hashing file";
391
+ fclose(file);
392
+ return false;
393
+ }
394
+
395
+ fclose(file);
396
+
397
+ unsigned char digest[32];
398
+ sha256_final(&ctx, digest);
399
+ if (out_sha256) {
400
+ *out_sha256 = ToHex(digest, sizeof(digest));
401
+ }
402
+
403
+ return true;
404
+ }
@@ -0,0 +1,56 @@
1
+ #pragma once
2
+
3
+ #include <string>
4
+ #include <functional>
5
+ #include <atomic>
6
+
7
+ /**
8
+ * Archive extraction helper using libarchive for fast tar.bz2 extraction
9
+ * Provides both C++ interface and JNI bindings
10
+ */
11
+ class ArchiveHelper {
12
+ public:
13
+ /**
14
+ * Extract tar.bz2 file to target directory
15
+ *
16
+ * @param sourcePath Path to the .tar.bz2 file
17
+ * @param targetPath Destination directory path
18
+ * @param force Whether to overwrite existing target directory
19
+ * @param onProgress Callback for progress updates (bytesExtracted, totalBytes, percent)
20
+ * @param outSha256 Optional output SHA-256 hex of the archive file
21
+ * @return true if extraction succeeded, false otherwise
22
+ */
23
+ static bool ExtractTarBz2(
24
+ const std::string& source_path,
25
+ const std::string& target_path,
26
+ bool force,
27
+ std::function<void(long long, long long, double)> on_progress = nullptr,
28
+ std::string* out_error = nullptr,
29
+ std::string* out_sha256 = nullptr);
30
+
31
+ /**
32
+ * Compute SHA-256 of a file.
33
+ *
34
+ * @param file_path Path to the file
35
+ * @param out_error Optional error message
36
+ * @param out_sha256 Output SHA-256 hex
37
+ * @return true if successful, false otherwise
38
+ */
39
+ static bool ComputeFileSha256(
40
+ const std::string& file_path,
41
+ std::string* out_error,
42
+ std::string* out_sha256);
43
+
44
+ /**
45
+ * Check if extraction has been cancelled
46
+ */
47
+ static bool IsCancelled();
48
+
49
+ /**
50
+ * Cancel ongoing extraction
51
+ */
52
+ static void Cancel();
53
+
54
+ private:
55
+ static std::atomic<bool> cancel_requested_;
56
+ };
@@ -0,0 +1,181 @@
1
+ /**
2
+ * sherpa-onnx-archive-jni.cpp
3
+ *
4
+ * Purpose: JNI bindings for SherpaOnnxArchiveHelper (Kotlin): nativeExtractTarBz2,
5
+ * nativeCancelExtract, nativeComputeFileSha256. Bridges to sherpa-onnx-archive-helper.cpp.
6
+ */
7
+ #include <jni.h>
8
+ #include <string>
9
+ #include <memory>
10
+ #include "sherpa-onnx-archive-helper.h"
11
+ #include <android/log.h>
12
+
13
+ static JavaVM* g_vm = nullptr;
14
+
15
+ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
16
+ g_vm = vm;
17
+ JNIEnv* env = nullptr;
18
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
19
+ return -1;
20
+ }
21
+ return JNI_VERSION_1_6;
22
+ }
23
+
24
+ extern "C" JNIEXPORT void JNICALL
25
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
26
+ JNIEnv* env,
27
+ jobject /* jthis */,
28
+ jstring j_source_path,
29
+ jstring j_target_path,
30
+ jboolean j_force,
31
+ jobject j_progress_callback,
32
+ jobject j_promise) {
33
+ const char* source_path = env->GetStringUTFChars(j_source_path, nullptr);
34
+ const char* target_path = env->GetStringUTFChars(j_target_path, nullptr);
35
+ std::string source_str(source_path);
36
+ std::string target_str(target_path);
37
+ env->ReleaseStringUTFChars(j_source_path, source_path);
38
+ env->ReleaseStringUTFChars(j_target_path, target_path);
39
+
40
+ // Get method for onProgress if callback provided
41
+ jmethodID on_progress_method = nullptr;
42
+ jobject j_progress_callback_global = nullptr;
43
+ if (j_progress_callback != nullptr) {
44
+ jclass callback_class = env->GetObjectClass(j_progress_callback);
45
+ on_progress_method = env->GetMethodID(
46
+ callback_class, "invoke", "(JJD)V");
47
+ env->DeleteLocalRef(callback_class);
48
+ // Store as global reference to ensure validity across potential thread boundaries
49
+ j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
50
+ }
51
+
52
+ // Get Promise.resolve and reject methods
53
+ jclass promise_class = env->GetObjectClass(j_promise);
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
+
58
+ // Get WritableMap from Arguments
59
+ jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
60
+ jmethodID create_map_method = env->GetStaticMethodID(
61
+ arguments_class, "createMap", "()Lcom/facebook/react/bridge/WritableMap;");
62
+ jobject result_map = env->CallStaticObjectMethod(arguments_class, create_map_method);
63
+
64
+ jclass writeable_map_class = env->FindClass("com/facebook/react/bridge/WritableMap");
65
+ jmethodID put_boolean_method = env->GetMethodID(
66
+ writeable_map_class, "putBoolean", "(Ljava/lang/String;Z)V");
67
+ jmethodID put_string_method = env->GetMethodID(
68
+ writeable_map_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
69
+
70
+ // Progress callback wrapper - JNI-safe version
71
+ auto on_progress = [j_progress_callback_global, on_progress_method](
72
+ long long bytes_extracted, long long total_bytes, double percent) {
73
+ if (j_progress_callback_global != nullptr && on_progress_method != nullptr) {
74
+ // Get JNIEnv for current thread
75
+ JNIEnv* callback_env = nullptr;
76
+ bool should_detach = false;
77
+
78
+ if (g_vm->GetEnv(reinterpret_cast<void**>(&callback_env), JNI_VERSION_1_6) == JNI_EDETACHED) {
79
+ // Thread not attached, attach it
80
+ if (g_vm->AttachCurrentThread(&callback_env, nullptr) == JNI_OK) {
81
+ should_detach = true;
82
+ } else {
83
+ return; // Failed to attach, skip callback
84
+ }
85
+ }
86
+
87
+ if (callback_env != nullptr) {
88
+ callback_env->CallVoidMethod(j_progress_callback_global, on_progress_method,
89
+ bytes_extracted, total_bytes, percent);
90
+
91
+ // Check and clear any exceptions from the callback
92
+ if (callback_env->ExceptionCheck()) {
93
+ callback_env->ExceptionClear();
94
+ }
95
+
96
+ // Detach if we attached in this call
97
+ if (should_detach) {
98
+ g_vm->DetachCurrentThread();
99
+ }
100
+ }
101
+ }
102
+ };
103
+
104
+ // Perform extraction
105
+ std::string error_msg;
106
+ std::string sha256;
107
+ bool success = ArchiveHelper::ExtractTarBz2(
108
+ source_str,
109
+ target_str,
110
+ j_force == JNI_TRUE,
111
+ on_progress,
112
+ &error_msg,
113
+ &sha256);
114
+
115
+ // Build result map
116
+ env->CallVoidMethod(result_map, put_boolean_method,
117
+ env->NewStringUTF("success"), success ? JNI_TRUE : JNI_FALSE);
118
+
119
+ if (success) {
120
+ env->CallVoidMethod(result_map, put_string_method,
121
+ env->NewStringUTF("path"), env->NewStringUTF(target_str.c_str()));
122
+ if (!sha256.empty()) {
123
+ env->CallVoidMethod(result_map, put_string_method,
124
+ env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
125
+ }
126
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
127
+ } else {
128
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
129
+ env->CallVoidMethod(result_map, put_string_method,
130
+ 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
+ }
135
+
136
+ // Clean up global reference
137
+ if (j_progress_callback_global != nullptr) {
138
+ env->DeleteGlobalRef(j_progress_callback_global);
139
+ }
140
+
141
+ env->DeleteLocalRef(result_map);
142
+ env->DeleteLocalRef(promise_class);
143
+ env->DeleteLocalRef(arguments_class);
144
+ env->DeleteLocalRef(writeable_map_class);
145
+ }
146
+
147
+ extern "C" JNIEXPORT void JNICALL
148
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeCancelExtract(JNIEnv* /* env */, jobject /* jthis */) {
149
+ ArchiveHelper::Cancel();
150
+ }
151
+
152
+ extern "C" JNIEXPORT void JNICALL
153
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeComputeFileSha256(
154
+ JNIEnv* env,
155
+ jobject /* jthis */,
156
+ jstring j_file_path,
157
+ jobject j_promise) {
158
+ const char* file_path = env->GetStringUTFChars(j_file_path, nullptr);
159
+ std::string file_str(file_path);
160
+ env->ReleaseStringUTFChars(j_file_path, file_path);
161
+
162
+ jclass promise_class = env->GetObjectClass(j_promise);
163
+ jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
164
+ jmethodID reject_method = env->GetMethodID(
165
+ promise_class, "reject", "(Ljava/lang/String;Ljava/lang/String;)V");
166
+
167
+ std::string error_msg;
168
+ std::string sha256;
169
+ bool success = ArchiveHelper::ComputeFileSha256(file_str, &error_msg, &sha256);
170
+
171
+ if (success) {
172
+ env->CallVoidMethod(j_promise, resolve_method, env->NewStringUTF(sha256.c_str()));
173
+ } else {
174
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[CHECKSUM_ERROR] %s", error_msg.c_str());
175
+ env->CallVoidMethod(j_promise, reject_method,
176
+ env->NewStringUTF("CHECKSUM_ERROR"),
177
+ env->NewStringUTF(error_msg.c_str()));
178
+ }
179
+
180
+ env->DeleteLocalRef(promise_class);
181
+ }