react-native-sherpa-onnx 0.3.4 → 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.
- package/README.md +2 -1
- package/SherpaOnnx.podspec +26 -5
- package/android/prebuilt-download.gradle +1 -1
- package/android/prebuilt-versions.gradle +1 -1
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +306 -6
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +33 -4
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +266 -7
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +100 -0
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxAssetHelper.kt +51 -6
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +60 -0
- package/ios/SherpaOnnx.mm +54 -1
- package/ios/archive/sherpa-onnx-archive-helper.h +7 -0
- package/ios/archive/sherpa-onnx-archive-helper.mm +18 -0
- package/lib/module/NativeSherpaOnnx.js.map +1 -1
- package/lib/module/download/extractTarZst.js +52 -0
- package/lib/module/download/extractTarZst.js.map +1 -0
- package/lib/module/download/index.js +1 -0
- package/lib/module/download/index.js.map +1 -1
- package/lib/module/extraction/index.js +191 -0
- package/lib/module/extraction/index.js.map +1 -0
- package/lib/module/extraction/types.js +2 -0
- package/lib/module/extraction/types.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeSherpaOnnx.d.ts +39 -0
- package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
- package/lib/typescript/src/download/extractTarZst.d.ts +14 -0
- package/lib/typescript/src/download/extractTarZst.d.ts.map +1 -0
- package/lib/typescript/src/download/index.d.ts +2 -0
- package/lib/typescript/src/download/index.d.ts.map +1 -1
- package/lib/typescript/src/extraction/index.d.ts +50 -0
- package/lib/typescript/src/extraction/index.d.ts.map +1 -0
- package/lib/typescript/src/extraction/types.d.ts +60 -0
- package/lib/typescript/src/extraction/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +6 -1
- package/scripts/check-model-csvs.sh +2 -2
- package/src/NativeSherpaOnnx.ts +56 -0
- package/src/download/extractTarZst.ts +77 -0
- package/src/download/index.ts +2 -0
- package/src/extraction/index.ts +273 -0
- package/src/extraction/types.ts +63 -0
- package/src/index.tsx +1 -0
- package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
- package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ To build the sherpa-onnx iOS XCFramework yourself (e.g. custom version or patche
|
|
|
96
96
|
| Text-to-Speech | ✅ **Supported** | Multiple model types (VITS, Matcha, Kokoro, etc.). See [Supported Model Types](#supported-model-types) and [TTS documentation](./docs/tts.md). |
|
|
97
97
|
| Streaming Text-to-Speech | ✅ **Supported** | Incremental speech generation for low time-to-first-byte and playback while generating. See [Streaming TTS](./docs/tts-streaming.md). |
|
|
98
98
|
| Execution providers (CPU, NNAPI, XNNPACK, Core ML, QNN) | ✅ **Supported** | See [Execution provider support](./docs/execution-providers.md). |
|
|
99
|
-
| Play Asset Delivery (PAD) | ✅ **Supported** | Android only. See [Model Setup](./docs/model-setup.md). |
|
|
99
|
+
| Play Asset Delivery (PAD) | ✅ **Supported** | Android only. See [Model Setup](./docs/model-setup.md) & [Extraction API](./docs/extraction.md). |
|
|
100
100
|
| Automatic Model type detection | ✅ **Supported** | `detectSttModel()` and `detectTtsModel()` for a path. See [Model Setup: Model type detection](./docs/model-setup.md#model-detection). |
|
|
101
101
|
| Model quantization | ✅ **Supported** | Automatic detection and preference for quantized (int8) models. |
|
|
102
102
|
| Flexible model loading | ✅ **Supported** | Asset models, file system models, or auto-detection. |
|
|
@@ -166,6 +166,7 @@ For **streaming TTS** (incremental generation, low latency), use `createStreamin
|
|
|
166
166
|
- [Source Separation](./docs/separation.md)
|
|
167
167
|
- [Model Setup](./docs/model-setup.md) – Bundled assets, Play Asset Delivery (PAD), model discovery APIs, and troubleshooting
|
|
168
168
|
- [Model Download Manager](./docs/download-manager.md)
|
|
169
|
+
- [Extraction API](./docs/extraction.md)
|
|
169
170
|
- [Disable FFMPEG](./docs/disable-ffmpeg.md)
|
|
170
171
|
- [Disable LIBARCHIVE](./docs/disable-libarchive.md)
|
|
171
172
|
|
package/SherpaOnnx.podspec
CHANGED
|
@@ -27,7 +27,13 @@ Pod::Spec.new do |s|
|
|
|
27
27
|
s.source = { :git => "https://github.com/XDcobra/react-native-sherpa-onnx.git", :tag => "#{s.version}" }
|
|
28
28
|
|
|
29
29
|
s.source_files = ["ios/**/*.{h,m,mm,swift,cpp}"]
|
|
30
|
-
|
|
30
|
+
# Exclude vendored framework headers from the compile/copy phases to avoid
|
|
31
|
+
# duplicate PrivateHeaders outputs when CocoaPods builds this pod as framework.
|
|
32
|
+
s.exclude_files = ["ios/Frameworks/**/*"]
|
|
33
|
+
private_headers = Dir.glob(File.join(pod_root, "ios", "**", "*.h")).reject do |path|
|
|
34
|
+
path.start_with?(File.join(pod_root, "ios", "Frameworks") + File::SEPARATOR)
|
|
35
|
+
end
|
|
36
|
+
s.private_header_files = private_headers.map { |path| path.sub("#{pod_root}/", "") }
|
|
31
37
|
|
|
32
38
|
s.frameworks = "Foundation", "Accelerate", "CoreML", "AVFoundation", "AudioToolbox"
|
|
33
39
|
|
|
@@ -61,9 +67,13 @@ Pod::Spec.new do |s|
|
|
|
61
67
|
libarchive_xcframework_root = File.join(pod_root, "ios", "Frameworks", "libarchive.xcframework")
|
|
62
68
|
libarchive_simulator_headers = File.join(libarchive_xcframework_root, "ios-arm64_x86_64-simulator", "Headers")
|
|
63
69
|
libarchive_device_headers = File.join(libarchive_xcframework_root, "ios-arm64", "Headers")
|
|
70
|
+
libarchive_simulator_slice = File.join(libarchive_xcframework_root, "ios-arm64_x86_64-simulator")
|
|
71
|
+
libarchive_device_slice = File.join(libarchive_xcframework_root, "ios-arm64")
|
|
64
72
|
|
|
65
73
|
ffmpeg_simulator_headers = File.join(ffmpeg_xcframework, "ios-arm64_x86_64-simulator", "Headers")
|
|
66
74
|
ffmpeg_device_headers = File.join(ffmpeg_xcframework, "ios-arm64", "Headers")
|
|
75
|
+
ffmpeg_simulator_slice = File.join(ffmpeg_xcframework, "ios-arm64_x86_64-simulator")
|
|
76
|
+
ffmpeg_device_slice = File.join(ffmpeg_xcframework, "ios-arm64")
|
|
67
77
|
|
|
68
78
|
gcc_defs = '$(inherited) PLATFORM_CONFIG_H=\\"libarchive_darwin_config.h\\"'
|
|
69
79
|
gcc_defs += ' HAVE_FFMPEG=1' if has_ffmpeg
|
|
@@ -97,22 +107,33 @@ Pod::Spec.new do |s|
|
|
|
97
107
|
header_search_paths << "\"#{ffmpeg_simulator_headers}\""
|
|
98
108
|
end
|
|
99
109
|
|
|
110
|
+
library_search_paths_ios = ["$(inherited)", "\"#{device_slice}\""]
|
|
111
|
+
library_search_paths_sim = ["$(inherited)", "\"#{simulator_slice}\""]
|
|
112
|
+
if has_ffmpeg
|
|
113
|
+
library_search_paths_ios << "\"#{ffmpeg_device_slice}\""
|
|
114
|
+
library_search_paths_sim << "\"#{ffmpeg_simulator_slice}\""
|
|
115
|
+
end
|
|
116
|
+
if has_libarchive
|
|
117
|
+
library_search_paths_ios << "\"#{libarchive_device_slice}\""
|
|
118
|
+
library_search_paths_sim << "\"#{libarchive_simulator_slice}\""
|
|
119
|
+
end
|
|
120
|
+
|
|
100
121
|
s.pod_target_xcconfig = {
|
|
101
122
|
"HEADER_SEARCH_PATHS" => header_search_paths.join(" "),
|
|
102
123
|
"GCC_PREPROCESSOR_DEFINITIONS" => gcc_defs,
|
|
103
124
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
|
104
125
|
"CLANG_CXX_LIBRARY" => "libc++",
|
|
105
126
|
"OTHER_CPLUSPLUSFLAGS" => "$(inherited)",
|
|
106
|
-
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" => "
|
|
107
|
-
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" => "
|
|
127
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" => library_search_paths_ios.join(" "),
|
|
128
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" => library_search_paths_sim.join(" "),
|
|
108
129
|
"OTHER_LDFLAGS" => ld_flags
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
s.user_target_xcconfig = {
|
|
112
133
|
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17",
|
|
113
134
|
"CLANG_CXX_LIBRARY" => "libc++",
|
|
114
|
-
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" => "
|
|
115
|
-
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" => "
|
|
135
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" => library_search_paths_ios.join(" "),
|
|
136
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" => library_search_paths_sim.join(" "),
|
|
116
137
|
"OTHER_LDFLAGS" => ld_flags
|
|
117
138
|
}
|
|
118
139
|
|
|
@@ -14,7 +14,7 @@ def requiredFfmpegSoFiles = [
|
|
|
14
14
|
def requiredSherpaOnnxSoFiles = [
|
|
15
15
|
'libsherpa-onnx-jni.so', 'libsherpa-onnx-c-api.so', 'libsherpa-onnx-cxx-api.so', 'libonnxruntime.so'
|
|
16
16
|
]
|
|
17
|
-
def requiredLibarchiveSoFiles = ['libarchive.so']
|
|
17
|
+
def requiredLibarchiveSoFiles = ['libarchive.so', 'libzstd.so']
|
|
18
18
|
def requiredOnnxruntimeJniSoFiles = ['libonnxruntime4j_jni.so']
|
|
19
19
|
|
|
20
20
|
def jniLibsDir = file("${project.projectDir}/src/main/jniLibs")
|
|
@@ -36,7 +36,7 @@ println "[react-native-sherpa-onnx] ffmpeg version (extracted/used): ${ffmpegVer
|
|
|
36
36
|
def libarchiveVersion = System.getenv('LIBARCHIVE_VERSION')
|
|
37
37
|
if (!libarchiveVersion) {
|
|
38
38
|
def v = readVersionFromTagFile(new File(moduleRoot, 'third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG'), 'libarchive-android-v')
|
|
39
|
-
libarchiveVersion = v ?: (project.hasProperty('libarchiveVersion') ? project.libarchiveVersion : '3.8.5')
|
|
39
|
+
libarchiveVersion = v ?: (project.hasProperty('libarchiveVersion') ? project.libarchiveVersion : '3.8.5-2')
|
|
40
40
|
}
|
|
41
41
|
project.ext.libarchiveVersion = libarchiveVersion
|
|
42
42
|
println "[react-native-sherpa-onnx] libarchive version (extracted/used): ${libarchiveVersion}"
|
|
@@ -75,6 +75,38 @@ static void DrainRemainingAndClose(ArchiveReadContext* ctx) {
|
|
|
75
75
|
fclose(ctx->file);
|
|
76
76
|
ctx->file = nullptr;
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
struct StreamReadContext {
|
|
80
|
+
std::array<unsigned char, 64 * 1024> buffer{};
|
|
81
|
+
Sha256Context sha_ctx{};
|
|
82
|
+
long long bytes_read = 0;
|
|
83
|
+
ArchiveHelper::StreamReadCallback read_cb = nullptr;
|
|
84
|
+
void* user_data = nullptr;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
static la_ssize_t ArchiveStreamReadCallback(struct archive* archive, void* client_data, const void** buff) {
|
|
88
|
+
auto* ctx = static_cast<StreamReadContext*>(client_data);
|
|
89
|
+
if (!ctx || !ctx->read_cb) {
|
|
90
|
+
archive_set_error(archive, EINVAL, "Invalid stream read context");
|
|
91
|
+
return -1;
|
|
92
|
+
}
|
|
93
|
+
std::ptrdiff_t n = ctx->read_cb(ctx->buffer.data(), ctx->buffer.size(), ctx->user_data);
|
|
94
|
+
if (n > 0) {
|
|
95
|
+
sha256_update(&ctx->sha_ctx, ctx->buffer.data(), static_cast<size_t>(n));
|
|
96
|
+
ctx->bytes_read += static_cast<long long>(n);
|
|
97
|
+
*buff = ctx->buffer.data();
|
|
98
|
+
return static_cast<la_ssize_t>(n);
|
|
99
|
+
}
|
|
100
|
+
if (n == 0) return 0;
|
|
101
|
+
archive_set_error(archive, EINVAL, "Stream read error");
|
|
102
|
+
return -1;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// No-op close callback for stream mode: the stream lifetime is managed by the
|
|
106
|
+
// caller (e.g. a JNI InputStream), so libarchive must not close it.
|
|
107
|
+
static int ArchiveStreamCloseCallback(struct archive* /* archive */, void* /* client_data */) {
|
|
108
|
+
return ARCHIVE_OK;
|
|
109
|
+
}
|
|
78
110
|
#endif // HAVE_LIBARCHIVE
|
|
79
111
|
|
|
80
112
|
static std::string ToHex(const unsigned char* data, size_t size) {
|
|
@@ -122,13 +154,15 @@ bool ArchiveHelper::ExtractTarBz2(
|
|
|
122
154
|
return false;
|
|
123
155
|
}
|
|
124
156
|
|
|
125
|
-
//
|
|
157
|
+
// If target exists and is a directory, extract into it (merge). Otherwise require empty or force-remove.
|
|
126
158
|
if (std::filesystem::exists(target_path)) {
|
|
127
|
-
if (
|
|
159
|
+
if (std::filesystem::is_directory(target_path)) {
|
|
160
|
+
// Merge: extract into existing directory (e.g. multiple archives → same base path)
|
|
161
|
+
} else if (force) {
|
|
128
162
|
std::error_code ec;
|
|
129
163
|
std::filesystem::remove_all(target_path, ec);
|
|
130
164
|
if (ec) {
|
|
131
|
-
if (out_error) *out_error = "Failed to remove target
|
|
165
|
+
if (out_error) *out_error = "Failed to remove target path: " + ec.message();
|
|
132
166
|
return false;
|
|
133
167
|
}
|
|
134
168
|
} else {
|
|
@@ -137,7 +171,6 @@ bool ArchiveHelper::ExtractTarBz2(
|
|
|
137
171
|
}
|
|
138
172
|
}
|
|
139
173
|
|
|
140
|
-
// Create target directory
|
|
141
174
|
std::error_code ec;
|
|
142
175
|
std::filesystem::create_directories(target_path, ec);
|
|
143
176
|
if (ec) {
|
|
@@ -167,11 +200,12 @@ bool ArchiveHelper::ExtractTarBz2(
|
|
|
167
200
|
return false;
|
|
168
201
|
}
|
|
169
202
|
|
|
170
|
-
// Configure archive to support tar and bzip2
|
|
203
|
+
// Configure archive to support tar and common compression (bzip2, gzip, xz, zstd)
|
|
171
204
|
archive_read_support_format_tar(archive);
|
|
172
205
|
archive_read_support_filter_bzip2(archive);
|
|
173
206
|
archive_read_support_filter_gzip(archive); // Also support gzip for compatibility
|
|
174
|
-
archive_read_support_filter_xz(archive);
|
|
207
|
+
archive_read_support_filter_xz(archive); // And xz
|
|
208
|
+
archive_read_support_filter_zstd(archive); // And zstd (.tar.zst)
|
|
175
209
|
|
|
176
210
|
ArchiveReadContext read_ctx;
|
|
177
211
|
read_ctx.file = fopen(source_path.c_str(), "rb");
|
|
@@ -354,6 +388,18 @@ bool ArchiveHelper::ExtractTarBz2(
|
|
|
354
388
|
close_reader();
|
|
355
389
|
return false;
|
|
356
390
|
}
|
|
391
|
+
|
|
392
|
+
result = archive_write_finish_entry(disk);
|
|
393
|
+
if (result != ARCHIVE_OK && result != ARCHIVE_WARN) {
|
|
394
|
+
const char* err = archive_error_string(disk);
|
|
395
|
+
if (out_error) {
|
|
396
|
+
*out_error = err ? std::string("Failed to finish entry: ") + err : "Failed to finish entry";
|
|
397
|
+
}
|
|
398
|
+
archive_read_free(archive);
|
|
399
|
+
archive_write_free(disk);
|
|
400
|
+
close_reader();
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
357
403
|
}
|
|
358
404
|
|
|
359
405
|
archive_read_free(archive);
|
|
@@ -376,6 +422,260 @@ bool ArchiveHelper::ExtractTarBz2(
|
|
|
376
422
|
#endif // HAVE_LIBARCHIVE
|
|
377
423
|
}
|
|
378
424
|
|
|
425
|
+
bool ArchiveHelper::ExtractTarZst(
|
|
426
|
+
const std::string& source_path,
|
|
427
|
+
const std::string& target_path,
|
|
428
|
+
bool force,
|
|
429
|
+
std::function<void(long long, long long, double)> on_progress,
|
|
430
|
+
std::string* out_error,
|
|
431
|
+
std::string* out_sha256) {
|
|
432
|
+
return ExtractTarBz2(source_path, target_path, force, on_progress, out_error, out_sha256);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
bool ArchiveHelper::ExtractFromStream(
|
|
436
|
+
StreamReadCallback read_cb,
|
|
437
|
+
void* read_user_data,
|
|
438
|
+
const std::string& target_path,
|
|
439
|
+
bool force,
|
|
440
|
+
std::function<void(long long, long long, double)> on_progress,
|
|
441
|
+
std::string* out_error,
|
|
442
|
+
std::string* out_sha256) {
|
|
443
|
+
cancel_requested_.store(false);
|
|
444
|
+
|
|
445
|
+
#ifndef HAVE_LIBARCHIVE
|
|
446
|
+
(void)read_cb;
|
|
447
|
+
(void)read_user_data;
|
|
448
|
+
(void)target_path;
|
|
449
|
+
(void)force;
|
|
450
|
+
(void)on_progress;
|
|
451
|
+
(void)out_sha256;
|
|
452
|
+
if (out_error) *out_error = "libarchive not available. Build with libarchive or set sherpaOnnxDisableLibarchive=false in gradle.properties. See docs/disable-libarchive.md.";
|
|
453
|
+
return false;
|
|
454
|
+
#else
|
|
455
|
+
if (!read_cb) {
|
|
456
|
+
if (out_error) *out_error = "Stream read callback is null";
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (std::filesystem::exists(target_path)) {
|
|
461
|
+
if (std::filesystem::is_directory(target_path)) {
|
|
462
|
+
// Merge: extract into existing directory (e.g. multiple archives → same base path)
|
|
463
|
+
} else if (force) {
|
|
464
|
+
std::error_code ec;
|
|
465
|
+
std::filesystem::remove_all(target_path, ec);
|
|
466
|
+
if (ec) {
|
|
467
|
+
if (out_error) *out_error = "Failed to remove target path: " + ec.message();
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
if (out_error) *out_error = "Target path already exists";
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
std::error_code ec;
|
|
477
|
+
std::filesystem::create_directories(target_path, ec);
|
|
478
|
+
if (ec) {
|
|
479
|
+
if (out_error) *out_error = "Failed to create target directory: " + ec.message();
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
#ifndef NDEBUG
|
|
484
|
+
__android_log_print(ANDROID_LOG_INFO, "SherpaOnnx",
|
|
485
|
+
"ExtractFromStream target_path=%s", target_path.c_str());
|
|
486
|
+
#endif
|
|
487
|
+
|
|
488
|
+
std::string canonical_target = std::filesystem::canonical(target_path).string();
|
|
489
|
+
if (canonical_target.back() != '/') canonical_target += '/';
|
|
490
|
+
|
|
491
|
+
const long long total_bytes = 0;
|
|
492
|
+
|
|
493
|
+
struct archive* archive = archive_read_new();
|
|
494
|
+
if (!archive) {
|
|
495
|
+
if (out_error) *out_error = "Failed to create archive reader";
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
archive_read_support_format_tar(archive);
|
|
500
|
+
archive_read_support_filter_bzip2(archive);
|
|
501
|
+
archive_read_support_filter_gzip(archive);
|
|
502
|
+
archive_read_support_filter_xz(archive);
|
|
503
|
+
archive_read_support_filter_zstd(archive);
|
|
504
|
+
|
|
505
|
+
StreamReadContext stream_ctx;
|
|
506
|
+
stream_ctx.read_cb = read_cb;
|
|
507
|
+
stream_ctx.user_data = read_user_data;
|
|
508
|
+
sha256_init(&stream_ctx.sha_ctx);
|
|
509
|
+
|
|
510
|
+
if (archive_read_open(archive, &stream_ctx, nullptr, ArchiveStreamReadCallback, ArchiveStreamCloseCallback) != ARCHIVE_OK) {
|
|
511
|
+
const char* err = archive_error_string(archive);
|
|
512
|
+
if (out_error) *out_error = err ? std::string("Failed to open archive: ") + err : "Failed to open archive";
|
|
513
|
+
archive_read_free(archive);
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
struct archive* disk = archive_write_disk_new();
|
|
518
|
+
if (!disk) {
|
|
519
|
+
if (out_error) *out_error = "Failed to create disk writer";
|
|
520
|
+
archive_read_free(archive);
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
archive_write_disk_set_options(disk,
|
|
525
|
+
ARCHIVE_EXTRACT_TIME |
|
|
526
|
+
ARCHIVE_EXTRACT_PERM |
|
|
527
|
+
ARCHIVE_EXTRACT_ACL |
|
|
528
|
+
ARCHIVE_EXTRACT_FFLAGS);
|
|
529
|
+
archive_write_disk_set_standard_lookup(disk);
|
|
530
|
+
|
|
531
|
+
struct archive_entry* entry = nullptr;
|
|
532
|
+
int result = ARCHIVE_OK;
|
|
533
|
+
long long extracted_bytes = 0;
|
|
534
|
+
int last_percent = -1;
|
|
535
|
+
long long last_emit_bytes = 0;
|
|
536
|
+
int entry_index = 0;
|
|
537
|
+
|
|
538
|
+
while ((result = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
|
|
539
|
+
if (cancel_requested_.load()) {
|
|
540
|
+
if (out_error) *out_error = "Extraction cancelled";
|
|
541
|
+
archive_read_free(archive);
|
|
542
|
+
archive_write_free(disk);
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const char* current_path = archive_entry_pathname(entry);
|
|
547
|
+
if (!current_path) {
|
|
548
|
+
archive_read_free(archive);
|
|
549
|
+
archive_write_free(disk);
|
|
550
|
+
if (out_error) *out_error = "Invalid entry path";
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
std::string entry_path(current_path);
|
|
555
|
+
std::string full_path = target_path;
|
|
556
|
+
if (full_path.back() != '/') full_path += '/';
|
|
557
|
+
full_path += entry_path;
|
|
558
|
+
|
|
559
|
+
std::string canonical_entry;
|
|
560
|
+
try {
|
|
561
|
+
std::filesystem::path p(full_path);
|
|
562
|
+
std::filesystem::path parent = p.parent_path();
|
|
563
|
+
if (std::filesystem::exists(parent)) {
|
|
564
|
+
canonical_entry = std::filesystem::canonical(parent).string();
|
|
565
|
+
} else {
|
|
566
|
+
while (!std::filesystem::exists(parent) && parent != parent.parent_path()) {
|
|
567
|
+
parent = parent.parent_path();
|
|
568
|
+
}
|
|
569
|
+
if (std::filesystem::exists(parent)) {
|
|
570
|
+
canonical_entry = std::filesystem::canonical(parent).string();
|
|
571
|
+
} else {
|
|
572
|
+
canonical_entry = canonical_target;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
canonical_entry += '/';
|
|
576
|
+
canonical_entry += p.filename().string();
|
|
577
|
+
} catch (const std::exception&) {
|
|
578
|
+
canonical_entry = full_path;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (canonical_entry.find(canonical_target) != 0) {
|
|
582
|
+
archive_read_free(archive);
|
|
583
|
+
archive_write_free(disk);
|
|
584
|
+
if (out_error) *out_error = "Blocked path traversal: " + entry_path;
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
archive_entry_set_pathname(entry, full_path.c_str());
|
|
589
|
+
|
|
590
|
+
result = archive_write_header(disk, entry);
|
|
591
|
+
if (result != ARCHIVE_OK) {
|
|
592
|
+
const char* err = archive_error_string(disk);
|
|
593
|
+
if (out_error) *out_error = err ? std::string("Failed to write entry: ") + err : "Failed to write entry";
|
|
594
|
+
archive_read_free(archive);
|
|
595
|
+
archive_write_free(disk);
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const void* buff = nullptr;
|
|
600
|
+
size_t size = 0;
|
|
601
|
+
la_int64_t offset = 0;
|
|
602
|
+
|
|
603
|
+
while ((result = archive_read_data_block(archive, &buff, &size, &offset)) == ARCHIVE_OK) {
|
|
604
|
+
if (cancel_requested_.load()) {
|
|
605
|
+
if (out_error) *out_error = "Extraction cancelled";
|
|
606
|
+
archive_read_free(archive);
|
|
607
|
+
archive_write_free(disk);
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
result = archive_write_data_block(disk, buff, size, offset);
|
|
612
|
+
if (result != ARCHIVE_OK) {
|
|
613
|
+
const char* err = archive_error_string(disk);
|
|
614
|
+
if (out_error) *out_error = err ? std::string("Failed to write data: ") + err : "Failed to write data";
|
|
615
|
+
archive_read_free(archive);
|
|
616
|
+
archive_write_free(disk);
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
extracted_bytes += static_cast<long long>(size);
|
|
621
|
+
|
|
622
|
+
if (on_progress) {
|
|
623
|
+
if (total_bytes > 0) {
|
|
624
|
+
long long compressed_bytes = archive_filter_bytes(archive, -1);
|
|
625
|
+
int percent = static_cast<int>(total_bytes > 0 ? (compressed_bytes * 100) / total_bytes : 0);
|
|
626
|
+
percent = (percent > 100) ? 100 : ((percent < 0) ? 0 : percent);
|
|
627
|
+
if (percent != last_percent) {
|
|
628
|
+
last_percent = percent;
|
|
629
|
+
on_progress(compressed_bytes, total_bytes, static_cast<double>(percent));
|
|
630
|
+
}
|
|
631
|
+
} else if (stream_ctx.bytes_read - last_emit_bytes >= 1024 * 1024) {
|
|
632
|
+
last_emit_bytes = stream_ctx.bytes_read;
|
|
633
|
+
on_progress(stream_ctx.bytes_read, 0, 0.0);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (result != ARCHIVE_EOF && result != ARCHIVE_OK) {
|
|
639
|
+
const char* err = archive_error_string(archive);
|
|
640
|
+
if (out_error) *out_error = err ? std::string("Failed to read data: ") + err : "Failed to read data";
|
|
641
|
+
archive_read_free(archive);
|
|
642
|
+
archive_write_free(disk);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
result = archive_write_finish_entry(disk);
|
|
647
|
+
if (result != ARCHIVE_OK && result != ARCHIVE_WARN) {
|
|
648
|
+
const char* err = archive_error_string(disk);
|
|
649
|
+
if (out_error) *out_error = err ? std::string("Failed to finish entry: ") + err : "Failed to finish entry";
|
|
650
|
+
archive_read_free(archive);
|
|
651
|
+
archive_write_free(disk);
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
entry_index++;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
#ifndef NDEBUG
|
|
658
|
+
__android_log_print(ANDROID_LOG_INFO, "SherpaOnnx",
|
|
659
|
+
"ExtractFromStream done entries=%d", entry_index);
|
|
660
|
+
#endif
|
|
661
|
+
|
|
662
|
+
archive_read_free(archive);
|
|
663
|
+
archive_write_free(disk);
|
|
664
|
+
|
|
665
|
+
if (out_sha256) {
|
|
666
|
+
unsigned char digest[32];
|
|
667
|
+
sha256_final(&stream_ctx.sha_ctx, digest);
|
|
668
|
+
*out_sha256 = ToHex(digest, sizeof(digest));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (on_progress) {
|
|
672
|
+
on_progress(stream_ctx.bytes_read, stream_ctx.bytes_read, 100.0);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return true;
|
|
676
|
+
#endif
|
|
677
|
+
}
|
|
678
|
+
|
|
379
679
|
bool ArchiveHelper::ComputeFileSha256(
|
|
380
680
|
const std::string& file_path,
|
|
381
681
|
std::string* out_error,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include <cstddef>
|
|
3
4
|
#include <string>
|
|
4
5
|
#include <functional>
|
|
5
6
|
#include <atomic>
|
|
@@ -10,14 +11,17 @@
|
|
|
10
11
|
*/
|
|
11
12
|
class ArchiveHelper {
|
|
12
13
|
public:
|
|
14
|
+
/** Callback to read bytes from a stream (e.g. Java InputStream via JNI). Returns bytes read, 0 on EOF, -1 on error. */
|
|
15
|
+
using StreamReadCallback = std::ptrdiff_t (*)(void* buf, size_t len, void* user_data);
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
|
-
* Extract tar.bz2 file to target directory
|
|
18
|
+
* Extract tar.bz2 (or .tar.zst, .tar.gz, .tar.xz) file to target directory.
|
|
15
19
|
*
|
|
16
|
-
* @param sourcePath Path to the
|
|
20
|
+
* @param sourcePath Path to the archive file
|
|
17
21
|
* @param targetPath Destination directory path
|
|
18
22
|
* @param force Whether to overwrite existing target directory
|
|
19
23
|
* @param onProgress Callback for progress updates (bytesExtracted, totalBytes, percent)
|
|
20
|
-
|
|
24
|
+
* @param outSha256 Optional output SHA-256 hex of the archive file
|
|
21
25
|
* @return true if extraction succeeded, false otherwise
|
|
22
26
|
*/
|
|
23
27
|
static bool ExtractTarBz2(
|
|
@@ -28,7 +32,32 @@ class ArchiveHelper {
|
|
|
28
32
|
std::string* out_error = nullptr,
|
|
29
33
|
std::string* out_sha256 = nullptr);
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Extract .tar.zst (or other supported tar compression) file to target directory.
|
|
37
|
+
* Uses the same implementation as ExtractTarBz2 (libarchive supports zstd when built with ENABLE_ZSTD).
|
|
38
|
+
*/
|
|
39
|
+
static bool ExtractTarZst(
|
|
40
|
+
const std::string& source_path,
|
|
41
|
+
const std::string& target_path,
|
|
42
|
+
bool force,
|
|
43
|
+
std::function<void(long long, long long, double)> on_progress = nullptr,
|
|
44
|
+
std::string* out_error = nullptr,
|
|
45
|
+
std::string* out_sha256 = nullptr);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract a tar archive (tar.zst or tar.bz2) from a stream via read_cb.
|
|
49
|
+
* Used for Android AssetManager streams; total_bytes can be 0 (progress then uses compressed bytes or periodic emit).
|
|
50
|
+
*/
|
|
51
|
+
static bool ExtractFromStream(
|
|
52
|
+
StreamReadCallback read_cb,
|
|
53
|
+
void* read_user_data,
|
|
54
|
+
const std::string& target_path,
|
|
55
|
+
bool force,
|
|
56
|
+
std::function<void(long long, long long, double)> on_progress = nullptr,
|
|
57
|
+
std::string* out_error = nullptr,
|
|
58
|
+
std::string* out_sha256 = nullptr);
|
|
59
|
+
|
|
60
|
+
/**
|
|
32
61
|
* Compute SHA-256 of a file.
|
|
33
62
|
*
|
|
34
63
|
* @param file_path Path to the file
|