react-native-buffered-blob 1.0.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/android/AGENTS.md +74 -0
- package/android/build.gradle +34 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/bufferedblob/BufferedBlobModule.kt +274 -0
- package/android/src/main/java/com/bufferedblob/BufferedBlobPackage.kt +32 -0
- package/android/src/main/java/com/bufferedblob/HandleRegistry.kt +84 -0
- package/android/src/main/java/com/bufferedblob/StreamingBridge.kt +211 -0
- package/cpp/AGENTS.md +71 -0
- package/cpp/AndroidPlatformBridge.cpp +437 -0
- package/cpp/AndroidPlatformBridge.h +79 -0
- package/cpp/BufferedBlobStreamingHostObject.cpp +344 -0
- package/cpp/BufferedBlobStreamingHostObject.h +118 -0
- package/cpp/CMakeLists.txt +49 -0
- package/cpp/jni_onload.cpp +32 -0
- package/ios/AGENTS.md +76 -0
- package/ios/BufferedBlobModule.h +44 -0
- package/ios/BufferedBlobModule.m +433 -0
- package/ios/BufferedBlobModule.mm +192 -0
- package/ios/BufferedBlobStreamingBridge.h +21 -0
- package/ios/BufferedBlobStreamingBridge.mm +442 -0
- package/ios/HandleRegistry.h +29 -0
- package/ios/HandleRegistry.m +67 -0
- package/ios/HandleTypes.h +83 -0
- package/ios/HandleTypes.m +333 -0
- package/lib/module/AGENTS.md +70 -0
- package/lib/module/NativeBufferedBlob.js +5 -0
- package/lib/module/NativeBufferedBlob.js.map +1 -0
- package/lib/module/api/AGENTS.md +62 -0
- package/lib/module/api/download.js +40 -0
- package/lib/module/api/download.js.map +1 -0
- package/lib/module/api/fileOps.js +70 -0
- package/lib/module/api/fileOps.js.map +1 -0
- package/lib/module/api/hash.js +13 -0
- package/lib/module/api/hash.js.map +1 -0
- package/lib/module/api/readFile.js +23 -0
- package/lib/module/api/readFile.js.map +1 -0
- package/lib/module/api/writeFile.js +18 -0
- package/lib/module/api/writeFile.js.map +1 -0
- package/lib/module/errors.js +45 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +25 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/module.js +19 -0
- package/lib/module/module.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/paths.js +32 -0
- package/lib/module/paths.js.map +1 -0
- package/lib/module/types.js +15 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/wrappers.js +107 -0
- package/lib/module/wrappers.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeBufferedBlob.d.ts +37 -0
- package/lib/typescript/src/NativeBufferedBlob.d.ts.map +1 -0
- package/lib/typescript/src/api/download.d.ts +13 -0
- package/lib/typescript/src/api/download.d.ts.map +1 -0
- package/lib/typescript/src/api/fileOps.d.ts +9 -0
- package/lib/typescript/src/api/fileOps.d.ts.map +1 -0
- package/lib/typescript/src/api/hash.d.ts +3 -0
- package/lib/typescript/src/api/hash.d.ts.map +1 -0
- package/lib/typescript/src/api/readFile.d.ts +3 -0
- package/lib/typescript/src/api/readFile.d.ts.map +1 -0
- package/lib/typescript/src/api/writeFile.d.ts +3 -0
- package/lib/typescript/src/api/writeFile.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +25 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +11 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/module.d.ts +23 -0
- package/lib/typescript/src/module.d.ts.map +1 -0
- package/lib/typescript/src/paths.d.ts +11 -0
- package/lib/typescript/src/paths.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +37 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/wrappers.d.ts +14 -0
- package/lib/typescript/src/wrappers.d.ts.map +1 -0
- package/package.json +114 -0
- package/react-native-buffered-blob.podspec +37 -0
- package/react-native.config.js +10 -0
- package/src/AGENTS.md +70 -0
- package/src/NativeBufferedBlob.ts +54 -0
- package/src/api/AGENTS.md +62 -0
- package/src/api/download.ts +46 -0
- package/src/api/fileOps.ts +83 -0
- package/src/api/hash.ts +14 -0
- package/src/api/readFile.ts +37 -0
- package/src/api/writeFile.ts +24 -0
- package/src/errors.ts +50 -0
- package/src/index.ts +28 -0
- package/src/module.ts +48 -0
- package/src/paths.ts +35 -0
- package/src/types.ts +42 -0
- package/src/wrappers.ts +123 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#include "AndroidPlatformBridge.h"
|
|
2
|
+
#include <fbjni/fbjni.h>
|
|
3
|
+
#include <cstring>
|
|
4
|
+
#include <thread>
|
|
5
|
+
#include <chrono>
|
|
6
|
+
|
|
7
|
+
namespace bufferedblob {
|
|
8
|
+
|
|
9
|
+
using namespace facebook;
|
|
10
|
+
|
|
11
|
+
// --- Thread Pool ---
|
|
12
|
+
|
|
13
|
+
void AndroidPlatformBridge::initThreadPool() {
|
|
14
|
+
for (size_t i = 0; i < kPoolThreads; ++i) {
|
|
15
|
+
poolWorkers_.emplace_back([this]() {
|
|
16
|
+
// Use fbjni::ThreadScope to attach this worker thread to the JVM.
|
|
17
|
+
// This registers the thread with fbjni's thread-local tracking so
|
|
18
|
+
// that Environment::current() works -- which is required by
|
|
19
|
+
// CallInvoker::invokeAsync() internally.
|
|
20
|
+
jni::ThreadScope threadScope;
|
|
21
|
+
while (true) {
|
|
22
|
+
std::function<void()> task;
|
|
23
|
+
{
|
|
24
|
+
std::unique_lock<std::mutex> lock(queueMutex_);
|
|
25
|
+
queueCV_.wait(lock, [this]() {
|
|
26
|
+
return shutdown_.load() || !taskQueue_.empty();
|
|
27
|
+
});
|
|
28
|
+
if (shutdown_.load() && taskQueue_.empty()) return;
|
|
29
|
+
task = std::move(taskQueue_.front());
|
|
30
|
+
taskQueue_.pop();
|
|
31
|
+
}
|
|
32
|
+
task();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void AndroidPlatformBridge::submitTask(std::function<void()> task) {
|
|
39
|
+
{
|
|
40
|
+
std::lock_guard<std::mutex> lock(queueMutex_);
|
|
41
|
+
taskQueue_.push(std::move(task));
|
|
42
|
+
}
|
|
43
|
+
queueCV_.notify_one();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
AndroidPlatformBridge::AndroidPlatformBridge(JNIEnv* env) {
|
|
47
|
+
env->GetJavaVM(&vm_);
|
|
48
|
+
auto clazz = env->FindClass("com/bufferedblob/StreamingBridge");
|
|
49
|
+
bridgeClass_ = (jclass)env->NewGlobalRef(clazz);
|
|
50
|
+
env->DeleteLocalRef(clazz);
|
|
51
|
+
initThreadPool();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
AndroidPlatformBridge::~AndroidPlatformBridge() {
|
|
55
|
+
shutdown_.store(true);
|
|
56
|
+
queueCV_.notify_all();
|
|
57
|
+
for (auto& worker : poolWorkers_) {
|
|
58
|
+
if (worker.joinable()) worker.join();
|
|
59
|
+
}
|
|
60
|
+
// Clean up global ref
|
|
61
|
+
if (bridgeClass_) {
|
|
62
|
+
JNIEnv* env = nullptr;
|
|
63
|
+
if (vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) == JNI_OK && env) {
|
|
64
|
+
env->DeleteGlobalRef(bridgeClass_);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- Read (uses thread pool) ---
|
|
70
|
+
|
|
71
|
+
void AndroidPlatformBridge::readNextChunk(
|
|
72
|
+
int handleId,
|
|
73
|
+
std::function<void(std::vector<uint8_t>)> onSuccess,
|
|
74
|
+
std::function<void()> onEOF,
|
|
75
|
+
std::function<void(std::string)> onError) {
|
|
76
|
+
// Capture raw jclass (global ref) -- no fbjni copy, safe from any thread.
|
|
77
|
+
jclass cls = bridgeClass_;
|
|
78
|
+
submitTask([cls, handleId, onSuccess = std::move(onSuccess),
|
|
79
|
+
onEOF = std::move(onEOF), onError = std::move(onError)]() {
|
|
80
|
+
try {
|
|
81
|
+
JNIEnv* env = jni::Environment::current();
|
|
82
|
+
|
|
83
|
+
jmethodID method = env->GetStaticMethodID(cls, "readNextChunk", "(I)[B");
|
|
84
|
+
if (!method) {
|
|
85
|
+
onError("readNextChunk method not found");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
auto result = (jbyteArray)env->CallStaticObjectMethod(cls, method, handleId);
|
|
90
|
+
|
|
91
|
+
if (env->ExceptionCheck()) {
|
|
92
|
+
jthrowable ex = env->ExceptionOccurred();
|
|
93
|
+
env->ExceptionClear();
|
|
94
|
+
jclass throwableClass = env->FindClass("java/lang/Throwable");
|
|
95
|
+
jmethodID getMessage = env->GetMethodID(
|
|
96
|
+
throwableClass, "getMessage", "()Ljava/lang/String;");
|
|
97
|
+
auto msg = (jstring)env->CallObjectMethod(ex, getMessage);
|
|
98
|
+
const char* msgChars = env->GetStringUTFChars(msg, nullptr);
|
|
99
|
+
std::string errorMsg(msgChars);
|
|
100
|
+
env->ReleaseStringUTFChars(msg, msgChars);
|
|
101
|
+
env->DeleteLocalRef(ex);
|
|
102
|
+
env->DeleteLocalRef(msg);
|
|
103
|
+
onError(errorMsg);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (result == nullptr) {
|
|
108
|
+
onEOF();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
jsize len = env->GetArrayLength(result);
|
|
113
|
+
std::vector<uint8_t> data(len);
|
|
114
|
+
jbyte* rawBytes = static_cast<jbyte*>(
|
|
115
|
+
env->GetPrimitiveArrayCritical(result, nullptr));
|
|
116
|
+
if (rawBytes) {
|
|
117
|
+
std::memcpy(data.data(), rawBytes, len);
|
|
118
|
+
env->ReleasePrimitiveArrayCritical(result, rawBytes, JNI_ABORT);
|
|
119
|
+
} else {
|
|
120
|
+
env->GetByteArrayRegion(
|
|
121
|
+
result, 0, len, reinterpret_cast<jbyte*>(data.data()));
|
|
122
|
+
}
|
|
123
|
+
env->DeleteLocalRef(result);
|
|
124
|
+
onSuccess(std::move(data));
|
|
125
|
+
} catch (const std::exception& e) {
|
|
126
|
+
onError(std::string("JNI error: ") + e.what());
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- Write (uses thread pool) ---
|
|
132
|
+
|
|
133
|
+
void AndroidPlatformBridge::write(
|
|
134
|
+
int handleId,
|
|
135
|
+
std::vector<uint8_t> data,
|
|
136
|
+
std::function<void(int)> onSuccess,
|
|
137
|
+
std::function<void(std::string)> onError) {
|
|
138
|
+
jclass cls = bridgeClass_;
|
|
139
|
+
|
|
140
|
+
submitTask([cls, handleId, dataCopy = std::move(data),
|
|
141
|
+
onSuccess = std::move(onSuccess),
|
|
142
|
+
onError = std::move(onError)]() {
|
|
143
|
+
try {
|
|
144
|
+
JNIEnv* env = jni::Environment::current();
|
|
145
|
+
|
|
146
|
+
jmethodID method = env->GetStaticMethodID(cls, "write", "(I[B)I");
|
|
147
|
+
if (!method) {
|
|
148
|
+
onError("write method not found");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
jbyteArray arr = env->NewByteArray(static_cast<jsize>(dataCopy.size()));
|
|
153
|
+
env->SetByteArrayRegion(
|
|
154
|
+
arr, 0, static_cast<jsize>(dataCopy.size()),
|
|
155
|
+
reinterpret_cast<const jbyte*>(dataCopy.data()));
|
|
156
|
+
|
|
157
|
+
jint result = env->CallStaticIntMethod(cls, method, handleId, arr);
|
|
158
|
+
env->DeleteLocalRef(arr);
|
|
159
|
+
|
|
160
|
+
if (env->ExceptionCheck()) {
|
|
161
|
+
jthrowable ex = env->ExceptionOccurred();
|
|
162
|
+
env->ExceptionClear();
|
|
163
|
+
jclass throwableClass = env->FindClass("java/lang/Throwable");
|
|
164
|
+
jmethodID getMessage = env->GetMethodID(
|
|
165
|
+
throwableClass, "getMessage", "()Ljava/lang/String;");
|
|
166
|
+
auto msg = (jstring)env->CallObjectMethod(ex, getMessage);
|
|
167
|
+
const char* msgChars = env->GetStringUTFChars(msg, nullptr);
|
|
168
|
+
std::string errorMsg(msgChars);
|
|
169
|
+
env->ReleaseStringUTFChars(msg, msgChars);
|
|
170
|
+
env->DeleteLocalRef(ex);
|
|
171
|
+
env->DeleteLocalRef(msg);
|
|
172
|
+
onError(errorMsg);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
onSuccess(static_cast<int>(result));
|
|
177
|
+
} catch (const std::exception& e) {
|
|
178
|
+
onError(std::string("JNI error: ") + e.what());
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// --- Flush (uses thread pool) ---
|
|
184
|
+
|
|
185
|
+
void AndroidPlatformBridge::flush(
|
|
186
|
+
int handleId,
|
|
187
|
+
std::function<void()> onSuccess,
|
|
188
|
+
std::function<void(std::string)> onError) {
|
|
189
|
+
jclass cls = bridgeClass_;
|
|
190
|
+
|
|
191
|
+
submitTask([cls, handleId, onSuccess = std::move(onSuccess),
|
|
192
|
+
onError = std::move(onError)]() {
|
|
193
|
+
try {
|
|
194
|
+
JNIEnv* env = jni::Environment::current();
|
|
195
|
+
|
|
196
|
+
jmethodID method = env->GetStaticMethodID(cls, "flush", "(I)V");
|
|
197
|
+
if (!method) {
|
|
198
|
+
onError("flush method not found");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
env->CallStaticVoidMethod(cls, method, handleId);
|
|
203
|
+
|
|
204
|
+
if (env->ExceptionCheck()) {
|
|
205
|
+
jthrowable ex = env->ExceptionOccurred();
|
|
206
|
+
env->ExceptionClear();
|
|
207
|
+
jclass throwableClass = env->FindClass("java/lang/Throwable");
|
|
208
|
+
jmethodID getMessage = env->GetMethodID(
|
|
209
|
+
throwableClass, "getMessage", "()Ljava/lang/String;");
|
|
210
|
+
auto msg = (jstring)env->CallObjectMethod(ex, getMessage);
|
|
211
|
+
const char* msgChars = env->GetStringUTFChars(msg, nullptr);
|
|
212
|
+
std::string errorMsg(msgChars);
|
|
213
|
+
env->ReleaseStringUTFChars(msg, msgChars);
|
|
214
|
+
env->DeleteLocalRef(ex);
|
|
215
|
+
env->DeleteLocalRef(msg);
|
|
216
|
+
onError(errorMsg);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
onSuccess();
|
|
221
|
+
} catch (const std::exception& e) {
|
|
222
|
+
onError(std::string("JNI error: ") + e.what());
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// --- Close (synchronous, called from JS thread) ---
|
|
228
|
+
|
|
229
|
+
void AndroidPlatformBridge::close(int handleId) {
|
|
230
|
+
try {
|
|
231
|
+
// Use raw JNI GetEnv -- this may be called from the JS thread which
|
|
232
|
+
// is not registered with fbjni's thread-local tracking.
|
|
233
|
+
JNIEnv* env = nullptr;
|
|
234
|
+
if (vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK || !env) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
jmethodID method = env->GetStaticMethodID(bridgeClass_, "close", "(I)V");
|
|
239
|
+
if (method) {
|
|
240
|
+
env->CallStaticVoidMethod(bridgeClass_, method, handleId);
|
|
241
|
+
if (env->ExceptionCheck()) {
|
|
242
|
+
env->ExceptionClear();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} catch (...) {
|
|
246
|
+
// Swallow close errors
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- Download (dedicated managed threads, NOT in pool) ---
|
|
251
|
+
|
|
252
|
+
void AndroidPlatformBridge::startDownload(
|
|
253
|
+
int handleId,
|
|
254
|
+
std::function<void(double, double, double)> onProgress,
|
|
255
|
+
std::function<void()> onSuccess,
|
|
256
|
+
std::function<void(std::string)> onError) {
|
|
257
|
+
// Capture raw jclass pointer -- avoids fbjni global_ref copy which calls
|
|
258
|
+
// Environment::current() and crashes on threads without fbjni TLData.
|
|
259
|
+
jclass cls = bridgeClass_;
|
|
260
|
+
|
|
261
|
+
auto done = std::make_shared<std::atomic<bool>>(false);
|
|
262
|
+
|
|
263
|
+
// Progress polling: timer thread sleeps, then submits JNI work to pool.
|
|
264
|
+
auto self = this;
|
|
265
|
+
std::thread([self, cls, handleId, onProgress, done]() {
|
|
266
|
+
while (!done->load()) {
|
|
267
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
268
|
+
if (done->load()) break;
|
|
269
|
+
|
|
270
|
+
self->submitTask([cls, handleId, onProgress, done]() {
|
|
271
|
+
if (done->load()) return;
|
|
272
|
+
try {
|
|
273
|
+
JNIEnv* pEnv = jni::Environment::current();
|
|
274
|
+
|
|
275
|
+
jmethodID getBytesMethod = pEnv->GetStaticMethodID(
|
|
276
|
+
cls, "getDownloadBytesDownloaded", "(I)J");
|
|
277
|
+
jmethodID getTotalMethod = pEnv->GetStaticMethodID(
|
|
278
|
+
cls, "getDownloadTotalBytes", "(I)J");
|
|
279
|
+
if (!getBytesMethod || !getTotalMethod) return;
|
|
280
|
+
|
|
281
|
+
jlong downloaded = pEnv->CallStaticLongMethod(
|
|
282
|
+
cls, getBytesMethod, handleId);
|
|
283
|
+
jlong total = pEnv->CallStaticLongMethod(
|
|
284
|
+
cls, getTotalMethod, handleId);
|
|
285
|
+
if (pEnv->ExceptionCheck()) { pEnv->ExceptionClear(); return; }
|
|
286
|
+
|
|
287
|
+
if (total > 0) {
|
|
288
|
+
double progress = static_cast<double>(downloaded) / static_cast<double>(total);
|
|
289
|
+
onProgress(static_cast<double>(downloaded),
|
|
290
|
+
static_cast<double>(total), progress);
|
|
291
|
+
} else if (downloaded > 0) {
|
|
292
|
+
onProgress(static_cast<double>(downloaded), -1.0, -1.0);
|
|
293
|
+
}
|
|
294
|
+
} catch (...) {
|
|
295
|
+
// Polling failure is non-fatal
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}).detach();
|
|
300
|
+
|
|
301
|
+
// Download thread: uses ThreadScope for fbjni-compatible attachment.
|
|
302
|
+
auto downloadThread = std::thread([cls, handleId, done,
|
|
303
|
+
onProgress = std::move(onProgress),
|
|
304
|
+
onSuccess = std::move(onSuccess),
|
|
305
|
+
onError = std::move(onError)]() {
|
|
306
|
+
try {
|
|
307
|
+
jni::ThreadScope threadScope;
|
|
308
|
+
JNIEnv* env = jni::Environment::current();
|
|
309
|
+
|
|
310
|
+
jmethodID method = env->GetStaticMethodID(cls, "startDownload", "(I)V");
|
|
311
|
+
if (!method) {
|
|
312
|
+
done->store(true);
|
|
313
|
+
onError("startDownload method not found");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
env->CallStaticVoidMethod(cls, method, handleId);
|
|
318
|
+
|
|
319
|
+
// Signal polling to stop
|
|
320
|
+
done->store(true);
|
|
321
|
+
|
|
322
|
+
if (env->ExceptionCheck()) {
|
|
323
|
+
jthrowable ex = env->ExceptionOccurred();
|
|
324
|
+
env->ExceptionClear();
|
|
325
|
+
jclass throwableClass = env->FindClass("java/lang/Throwable");
|
|
326
|
+
jmethodID getMessage = env->GetMethodID(
|
|
327
|
+
throwableClass, "getMessage", "()Ljava/lang/String;");
|
|
328
|
+
auto msg = (jstring)env->CallObjectMethod(ex, getMessage);
|
|
329
|
+
const char* msgChars = env->GetStringUTFChars(msg, nullptr);
|
|
330
|
+
std::string errorMsg(msgChars);
|
|
331
|
+
env->ReleaseStringUTFChars(msg, msgChars);
|
|
332
|
+
env->DeleteLocalRef(ex);
|
|
333
|
+
env->DeleteLocalRef(msg);
|
|
334
|
+
onError(errorMsg);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Read final progress values for a 100% callback
|
|
339
|
+
jmethodID getBytesMethod = env->GetStaticMethodID(
|
|
340
|
+
cls, "getDownloadBytesDownloaded", "(I)J");
|
|
341
|
+
jmethodID getTotalMethod = env->GetStaticMethodID(
|
|
342
|
+
cls, "getDownloadTotalBytes", "(I)J");
|
|
343
|
+
if (getBytesMethod && getTotalMethod) {
|
|
344
|
+
jlong finalDownloaded = env->CallStaticLongMethod(
|
|
345
|
+
cls, getBytesMethod, handleId);
|
|
346
|
+
jlong finalTotal = env->CallStaticLongMethod(
|
|
347
|
+
cls, getTotalMethod, handleId);
|
|
348
|
+
if (!env->ExceptionCheck() && finalTotal > 0) {
|
|
349
|
+
onProgress(static_cast<double>(finalDownloaded),
|
|
350
|
+
static_cast<double>(finalTotal), 1.0);
|
|
351
|
+
}
|
|
352
|
+
if (env->ExceptionCheck()) env->ExceptionClear();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
onSuccess();
|
|
356
|
+
} catch (const std::exception& e) {
|
|
357
|
+
done->store(true);
|
|
358
|
+
onError(std::string("JNI error: ") + e.what());
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
downloadThread.detach();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
void AndroidPlatformBridge::cancelDownload(int handleId) {
|
|
365
|
+
try {
|
|
366
|
+
JNIEnv* env = nullptr;
|
|
367
|
+
if (vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK || !env) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
jmethodID method = env->GetStaticMethodID(bridgeClass_, "cancelDownload", "(I)V");
|
|
372
|
+
if (method) {
|
|
373
|
+
env->CallStaticVoidMethod(bridgeClass_, method, handleId);
|
|
374
|
+
if (env->ExceptionCheck()) {
|
|
375
|
+
env->ExceptionClear();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch (...) {
|
|
379
|
+
// Swallow cancel errors
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
PlatformBridge::ReaderInfo AndroidPlatformBridge::getReaderInfo(int handleId) {
|
|
384
|
+
ReaderInfo info{0, 0, false};
|
|
385
|
+
try {
|
|
386
|
+
JNIEnv* env = nullptr;
|
|
387
|
+
if (vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK || !env) {
|
|
388
|
+
return info;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
jmethodID method = env->GetStaticMethodID(bridgeClass_, "getReaderFileSize", "(I)J");
|
|
392
|
+
if (method) {
|
|
393
|
+
info.fileSize = static_cast<double>(
|
|
394
|
+
env->CallStaticLongMethod(bridgeClass_, method, handleId));
|
|
395
|
+
if (env->ExceptionCheck()) env->ExceptionClear();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
method = env->GetStaticMethodID(bridgeClass_, "getReaderBytesRead", "(I)J");
|
|
399
|
+
if (method) {
|
|
400
|
+
info.bytesRead = static_cast<double>(
|
|
401
|
+
env->CallStaticLongMethod(bridgeClass_, method, handleId));
|
|
402
|
+
if (env->ExceptionCheck()) env->ExceptionClear();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
method = env->GetStaticMethodID(bridgeClass_, "getReaderIsEOF", "(I)Z");
|
|
406
|
+
if (method) {
|
|
407
|
+
info.isEOF = env->CallStaticBooleanMethod(bridgeClass_, method, handleId);
|
|
408
|
+
if (env->ExceptionCheck()) env->ExceptionClear();
|
|
409
|
+
}
|
|
410
|
+
} catch (...) {
|
|
411
|
+
// Return default info
|
|
412
|
+
}
|
|
413
|
+
return info;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
PlatformBridge::WriterInfo AndroidPlatformBridge::getWriterInfo(int handleId) {
|
|
417
|
+
WriterInfo info{0};
|
|
418
|
+
try {
|
|
419
|
+
JNIEnv* env = nullptr;
|
|
420
|
+
if (vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK || !env) {
|
|
421
|
+
return info;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
jmethodID method = env->GetStaticMethodID(
|
|
425
|
+
bridgeClass_, "getWriterBytesWritten", "(I)J");
|
|
426
|
+
if (method) {
|
|
427
|
+
info.bytesWritten = static_cast<double>(
|
|
428
|
+
env->CallStaticLongMethod(bridgeClass_, method, handleId));
|
|
429
|
+
if (env->ExceptionCheck()) env->ExceptionClear();
|
|
430
|
+
}
|
|
431
|
+
} catch (...) {
|
|
432
|
+
// Return default info
|
|
433
|
+
}
|
|
434
|
+
return info;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
} // namespace bufferedblob
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "BufferedBlobStreamingHostObject.h"
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
#include <ReactCommon/CallInvoker.h>
|
|
7
|
+
#include <queue>
|
|
8
|
+
#include <mutex>
|
|
9
|
+
#include <condition_variable>
|
|
10
|
+
#include <thread>
|
|
11
|
+
#include <functional>
|
|
12
|
+
#include <atomic>
|
|
13
|
+
|
|
14
|
+
namespace bufferedblob {
|
|
15
|
+
|
|
16
|
+
using namespace facebook;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Android implementation of PlatformBridge.
|
|
20
|
+
* Calls into Kotlin/Java HandleRegistry via JNI to perform streaming operations.
|
|
21
|
+
* Uses a bounded thread pool for read/write/flush; downloads use dedicated threads.
|
|
22
|
+
*/
|
|
23
|
+
class AndroidPlatformBridge : public PlatformBridge {
|
|
24
|
+
public:
|
|
25
|
+
AndroidPlatformBridge(JNIEnv* env);
|
|
26
|
+
~AndroidPlatformBridge() override;
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
void readNextChunk(
|
|
30
|
+
int handleId,
|
|
31
|
+
std::function<void(std::vector<uint8_t>)> onSuccess,
|
|
32
|
+
std::function<void()> onEOF,
|
|
33
|
+
std::function<void(std::string)> onError
|
|
34
|
+
) override;
|
|
35
|
+
|
|
36
|
+
void write(
|
|
37
|
+
int handleId,
|
|
38
|
+
std::vector<uint8_t> data,
|
|
39
|
+
std::function<void(int)> onSuccess,
|
|
40
|
+
std::function<void(std::string)> onError
|
|
41
|
+
) override;
|
|
42
|
+
|
|
43
|
+
void flush(
|
|
44
|
+
int handleId,
|
|
45
|
+
std::function<void()> onSuccess,
|
|
46
|
+
std::function<void(std::string)> onError
|
|
47
|
+
) override;
|
|
48
|
+
|
|
49
|
+
void close(int handleId) override;
|
|
50
|
+
|
|
51
|
+
void startDownload(
|
|
52
|
+
int handleId,
|
|
53
|
+
std::function<void(double, double, double)> onProgress,
|
|
54
|
+
std::function<void()> onSuccess,
|
|
55
|
+
std::function<void(std::string)> onError
|
|
56
|
+
) override;
|
|
57
|
+
|
|
58
|
+
void cancelDownload(int handleId) override;
|
|
59
|
+
|
|
60
|
+
ReaderInfo getReaderInfo(int handleId) override;
|
|
61
|
+
WriterInfo getWriterInfo(int handleId) override;
|
|
62
|
+
|
|
63
|
+
private:
|
|
64
|
+
JavaVM* vm_;
|
|
65
|
+
jclass bridgeClass_{nullptr};
|
|
66
|
+
|
|
67
|
+
// Thread pool for read/write/flush (not downloads)
|
|
68
|
+
static constexpr size_t kPoolThreads = 4;
|
|
69
|
+
std::vector<std::thread> poolWorkers_;
|
|
70
|
+
std::queue<std::function<void()>> taskQueue_;
|
|
71
|
+
std::mutex queueMutex_;
|
|
72
|
+
std::condition_variable queueCV_;
|
|
73
|
+
std::atomic<bool> shutdown_{false};
|
|
74
|
+
|
|
75
|
+
void initThreadPool();
|
|
76
|
+
void submitTask(std::function<void()> task);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
} // namespace bufferedblob
|