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.
Files changed (93) hide show
  1. package/android/AGENTS.md +74 -0
  2. package/android/build.gradle +34 -0
  3. package/android/src/main/AndroidManifest.xml +4 -0
  4. package/android/src/main/java/com/bufferedblob/BufferedBlobModule.kt +274 -0
  5. package/android/src/main/java/com/bufferedblob/BufferedBlobPackage.kt +32 -0
  6. package/android/src/main/java/com/bufferedblob/HandleRegistry.kt +84 -0
  7. package/android/src/main/java/com/bufferedblob/StreamingBridge.kt +211 -0
  8. package/cpp/AGENTS.md +71 -0
  9. package/cpp/AndroidPlatformBridge.cpp +437 -0
  10. package/cpp/AndroidPlatformBridge.h +79 -0
  11. package/cpp/BufferedBlobStreamingHostObject.cpp +344 -0
  12. package/cpp/BufferedBlobStreamingHostObject.h +118 -0
  13. package/cpp/CMakeLists.txt +49 -0
  14. package/cpp/jni_onload.cpp +32 -0
  15. package/ios/AGENTS.md +76 -0
  16. package/ios/BufferedBlobModule.h +44 -0
  17. package/ios/BufferedBlobModule.m +433 -0
  18. package/ios/BufferedBlobModule.mm +192 -0
  19. package/ios/BufferedBlobStreamingBridge.h +21 -0
  20. package/ios/BufferedBlobStreamingBridge.mm +442 -0
  21. package/ios/HandleRegistry.h +29 -0
  22. package/ios/HandleRegistry.m +67 -0
  23. package/ios/HandleTypes.h +83 -0
  24. package/ios/HandleTypes.m +333 -0
  25. package/lib/module/AGENTS.md +70 -0
  26. package/lib/module/NativeBufferedBlob.js +5 -0
  27. package/lib/module/NativeBufferedBlob.js.map +1 -0
  28. package/lib/module/api/AGENTS.md +62 -0
  29. package/lib/module/api/download.js +40 -0
  30. package/lib/module/api/download.js.map +1 -0
  31. package/lib/module/api/fileOps.js +70 -0
  32. package/lib/module/api/fileOps.js.map +1 -0
  33. package/lib/module/api/hash.js +13 -0
  34. package/lib/module/api/hash.js.map +1 -0
  35. package/lib/module/api/readFile.js +23 -0
  36. package/lib/module/api/readFile.js.map +1 -0
  37. package/lib/module/api/writeFile.js +18 -0
  38. package/lib/module/api/writeFile.js.map +1 -0
  39. package/lib/module/errors.js +45 -0
  40. package/lib/module/errors.js.map +1 -0
  41. package/lib/module/index.js +25 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/module.js +19 -0
  44. package/lib/module/module.js.map +1 -0
  45. package/lib/module/package.json +1 -0
  46. package/lib/module/paths.js +32 -0
  47. package/lib/module/paths.js.map +1 -0
  48. package/lib/module/types.js +15 -0
  49. package/lib/module/types.js.map +1 -0
  50. package/lib/module/wrappers.js +107 -0
  51. package/lib/module/wrappers.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/NativeBufferedBlob.d.ts +37 -0
  54. package/lib/typescript/src/NativeBufferedBlob.d.ts.map +1 -0
  55. package/lib/typescript/src/api/download.d.ts +13 -0
  56. package/lib/typescript/src/api/download.d.ts.map +1 -0
  57. package/lib/typescript/src/api/fileOps.d.ts +9 -0
  58. package/lib/typescript/src/api/fileOps.d.ts.map +1 -0
  59. package/lib/typescript/src/api/hash.d.ts +3 -0
  60. package/lib/typescript/src/api/hash.d.ts.map +1 -0
  61. package/lib/typescript/src/api/readFile.d.ts +3 -0
  62. package/lib/typescript/src/api/readFile.d.ts.map +1 -0
  63. package/lib/typescript/src/api/writeFile.d.ts +3 -0
  64. package/lib/typescript/src/api/writeFile.d.ts.map +1 -0
  65. package/lib/typescript/src/errors.d.ts +25 -0
  66. package/lib/typescript/src/errors.d.ts.map +1 -0
  67. package/lib/typescript/src/index.d.ts +11 -0
  68. package/lib/typescript/src/index.d.ts.map +1 -0
  69. package/lib/typescript/src/module.d.ts +23 -0
  70. package/lib/typescript/src/module.d.ts.map +1 -0
  71. package/lib/typescript/src/paths.d.ts +11 -0
  72. package/lib/typescript/src/paths.d.ts.map +1 -0
  73. package/lib/typescript/src/types.d.ts +37 -0
  74. package/lib/typescript/src/types.d.ts.map +1 -0
  75. package/lib/typescript/src/wrappers.d.ts +14 -0
  76. package/lib/typescript/src/wrappers.d.ts.map +1 -0
  77. package/package.json +114 -0
  78. package/react-native-buffered-blob.podspec +37 -0
  79. package/react-native.config.js +10 -0
  80. package/src/AGENTS.md +70 -0
  81. package/src/NativeBufferedBlob.ts +54 -0
  82. package/src/api/AGENTS.md +62 -0
  83. package/src/api/download.ts +46 -0
  84. package/src/api/fileOps.ts +83 -0
  85. package/src/api/hash.ts +14 -0
  86. package/src/api/readFile.ts +37 -0
  87. package/src/api/writeFile.ts +24 -0
  88. package/src/errors.ts +50 -0
  89. package/src/index.ts +28 -0
  90. package/src/module.ts +48 -0
  91. package/src/paths.ts +35 -0
  92. package/src/types.ts +42 -0
  93. 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