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.
- package/README.md +232 -236
- package/SherpaOnnx.podspec +68 -64
- package/android/build.gradle +182 -192
- package/android/codegen.gradle +57 -0
- package/android/prebuilt-download.gradle +428 -0
- package/android/prebuilt-versions.gradle +43 -0
- package/android/proguard-rules.pro +10 -0
- package/android/src/main/assets/testModels/add_mul_add.onnx +28 -0
- package/android/src/main/assets/testModels/nnapi_internal_uint8_support.onnx +0 -0
- package/android/src/main/assets/testModels/qnn_multi_ctx_embed.onnx +0 -0
- package/android/src/main/cpp/CMakeLists.txt +166 -129
- package/android/src/main/cpp/CMakePresets.json +54 -0
- package/android/src/main/cpp/crypto/sha256.cpp +174 -0
- package/android/src/main/cpp/crypto/sha256.h +16 -0
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +404 -0
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +56 -0
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +181 -0
- package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +888 -0
- package/{ios → android/src/main/cpp/jni/model_detect}/sherpa-onnx-common.h +18 -18
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.cpp +86 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.h +20 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.cpp +423 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.h +55 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-stt.cpp +399 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +238 -0
- package/{ios → android/src/main/cpp/jni/model_detect}/sherpa-onnx-model-detect.h +122 -89
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.cpp +99 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.h +16 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.cpp +78 -0
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.h +16 -0
- package/android/src/main/cpp/jni/module/sherpa-onnx-module-jni.cpp +190 -0
- package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +301 -0
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +94 -0
- package/android/src/main/java/com/sherpaonnx/{SherpaOnnxCoreHelper.kt → SherpaOnnxAssetHelper.kt} +350 -236
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +791 -483
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxSttHelper.kt +699 -109
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +1123 -668
- package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +187 -0
- package/ios/SherpaOnnx+Assets.h +11 -0
- package/ios/SherpaOnnx+Assets.mm +325 -0
- package/ios/SherpaOnnx+STT.mm +455 -118
- package/ios/SherpaOnnx+TTS.mm +1101 -712
- package/ios/SherpaOnnx.h +17 -6
- package/ios/SherpaOnnx.mm +206 -311
- package/ios/SherpaOnnx.xcconfig +19 -19
- package/ios/SherpaOnnxCoreMLHelper.swift +24 -0
- package/ios/archive/sherpa-onnx-archive-helper.h +21 -0
- package/ios/archive/sherpa-onnx-archive-helper.mm +296 -0
- package/ios/libarchive_darwin_config.h +153 -0
- package/{android/src/main/cpp/jni → ios/model_detect}/sherpa-onnx-common.h +18 -18
- package/ios/model_detect/sherpa-onnx-model-detect-helper.h +49 -0
- package/ios/model_detect/sherpa-onnx-model-detect-helper.mm +210 -0
- package/ios/model_detect/sherpa-onnx-model-detect-stt.mm +344 -0
- package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +201 -0
- package/{android/src/main/cpp/jni → ios/model_detect}/sherpa-onnx-model-detect.h +117 -89
- package/ios/scripts/patch-libarchive-includes.sh +61 -0
- package/ios/scripts/setup-ios-libarchive.sh +98 -0
- package/ios/stt/sherpa-onnx-stt-wrapper.h +129 -0
- package/ios/stt/sherpa-onnx-stt-wrapper.mm +523 -0
- package/ios/{sherpa-onnx-tts-wrapper.h → tts/sherpa-onnx-tts-wrapper.h} +90 -85
- package/ios/{sherpa-onnx-tts-wrapper.mm → tts/sherpa-onnx-tts-wrapper.mm} +376 -345
- package/lib/module/NativeSherpaOnnx.js +3 -0
- package/lib/module/NativeSherpaOnnx.js.map +1 -1
- package/lib/module/audio/index.js +22 -0
- package/lib/module/audio/index.js.map +1 -0
- package/lib/module/diarization/index.js +1 -1
- package/lib/module/diarization/index.js.map +1 -1
- package/lib/module/download/ModelDownloadManager.js +918 -0
- package/lib/module/download/ModelDownloadManager.js.map +1 -0
- package/lib/module/download/extractTarBz2.js +53 -0
- package/lib/module/download/extractTarBz2.js.map +1 -0
- package/lib/module/download/index.js +6 -0
- package/lib/module/download/index.js.map +1 -0
- package/lib/module/download/validation.js +178 -0
- package/lib/module/download/validation.js.map +1 -0
- package/lib/module/enhancement/index.js +1 -1
- package/lib/module/enhancement/index.js.map +1 -1
- package/lib/module/index.js +41 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/separation/index.js +1 -1
- package/lib/module/separation/index.js.map +1 -1
- package/lib/module/stt/index.js +127 -60
- package/lib/module/stt/index.js.map +1 -1
- package/lib/module/stt/sttModelLanguages.js +512 -0
- package/lib/module/stt/sttModelLanguages.js.map +1 -0
- package/lib/module/stt/types.js +53 -1
- package/lib/module/stt/types.js.map +1 -1
- package/lib/module/tts/index.js +216 -289
- package/lib/module/tts/index.js.map +1 -1
- package/lib/module/tts/types.js +86 -1
- package/lib/module/tts/types.js.map +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/module/utils.js +86 -73
- package/lib/module/utils.js.map +1 -1
- package/lib/module/vad/index.js +1 -1
- package/lib/module/vad/index.js.map +1 -1
- package/lib/typescript/src/NativeSherpaOnnx.d.ts +192 -38
- package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
- package/lib/typescript/src/audio/index.d.ts +13 -0
- package/lib/typescript/src/audio/index.d.ts.map +1 -0
- package/lib/typescript/src/diarization/index.d.ts +3 -2
- package/lib/typescript/src/diarization/index.d.ts.map +1 -1
- package/lib/typescript/src/download/ModelDownloadManager.d.ts +108 -0
- package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -0
- package/lib/typescript/src/download/extractTarBz2.d.ts +14 -0
- package/lib/typescript/src/download/extractTarBz2.d.ts.map +1 -0
- package/lib/typescript/src/download/index.d.ts +7 -0
- package/lib/typescript/src/download/index.d.ts.map +1 -0
- package/lib/typescript/src/download/validation.d.ts +57 -0
- package/lib/typescript/src/download/validation.d.ts.map +1 -0
- package/lib/typescript/src/enhancement/index.d.ts +3 -2
- package/lib/typescript/src/enhancement/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +26 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/separation/index.d.ts +3 -2
- package/lib/typescript/src/separation/index.d.ts.map +1 -1
- package/lib/typescript/src/stt/index.d.ts +31 -43
- package/lib/typescript/src/stt/index.d.ts.map +1 -1
- package/lib/typescript/src/stt/sttModelLanguages.d.ts +52 -0
- package/lib/typescript/src/stt/sttModelLanguages.d.ts.map +1 -0
- package/lib/typescript/src/stt/types.d.ts +196 -9
- package/lib/typescript/src/stt/types.d.ts.map +1 -1
- package/lib/typescript/src/tts/index.d.ts +25 -211
- package/lib/typescript/src/tts/index.d.ts.map +1 -1
- package/lib/typescript/src/tts/types.d.ts +148 -25
- package/lib/typescript/src/tts/types.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +0 -32
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts +28 -13
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/lib/typescript/src/vad/index.d.ts +3 -2
- package/lib/typescript/src/vad/index.d.ts.map +1 -1
- package/package.json +250 -222
- package/scripts/check-qnn-support.sh +78 -0
- package/scripts/setup-ios-framework.sh +379 -282
- package/src/NativeSherpaOnnx.ts +474 -251
- package/src/audio/index.ts +32 -0
- package/src/diarization/index.ts +4 -2
- package/src/download/ModelDownloadManager.ts +1325 -0
- package/src/download/extractTarBz2.ts +78 -0
- package/src/download/index.ts +43 -0
- package/src/download/validation.ts +279 -0
- package/src/enhancement/index.ts +4 -2
- package/src/index.tsx +78 -27
- package/src/separation/index.ts +4 -2
- package/src/stt/index.ts +249 -89
- package/src/stt/sttModelLanguages.ts +237 -0
- package/src/stt/types.ts +263 -9
- package/src/tts/index.ts +470 -458
- package/src/tts/types.ts +373 -218
- package/src/types.ts +0 -44
- package/src/utils.ts +145 -131
- package/src/vad/index.ts +4 -2
- package/third_party/ffmpeg_prebuilt/ANDROID_RELEASE_TAG +1 -0
- package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -0
- package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -0
- package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -0
- package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -0
- package/android/src/main/cpp/include/sherpa-onnx/c-api/c-api.h +0 -1918
- package/android/src/main/cpp/include/sherpa-onnx/c-api/cxx-api.h +0 -841
- package/android/src/main/cpp/jni/sherpa-onnx-model-detect.cpp +0 -541
- package/android/src/main/cpp/jni/sherpa-onnx-stt-jni.cpp +0 -336
- package/android/src/main/cpp/jni/sherpa-onnx-stt-wrapper.cpp +0 -222
- package/android/src/main/cpp/jni/sherpa-onnx-stt-wrapper.h +0 -68
- package/android/src/main/cpp/jni/sherpa-onnx-tts-jni.cpp +0 -823
- package/android/src/main/cpp/jni/sherpa-onnx-tts-wrapper.cpp +0 -387
- package/android/src/main/cpp/jni/sherpa-onnx-tts-wrapper.h +0 -147
- package/ios/Frameworks/sherpa_onnx.xcframework.zip +0 -0
- package/ios/include/sherpa-onnx/c-api/c-api.h +0 -1918
- package/ios/include/sherpa-onnx/c-api/cxx-api.h +0 -841
- package/ios/sherpa-onnx-model-detect.mm +0 -441
- package/ios/sherpa-onnx-stt-wrapper.h +0 -48
- package/ios/sherpa-onnx-stt-wrapper.mm +0 -201
- package/scripts/copy-headers.js +0 -184
- 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
|
+
}
|