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,433 @@
|
|
|
1
|
+
#import "BufferedBlobModule.h"
|
|
2
|
+
#import "HandleRegistry.h"
|
|
3
|
+
#import "HandleTypes.h"
|
|
4
|
+
#import <CommonCrypto/CommonDigest.h>
|
|
5
|
+
|
|
6
|
+
@implementation BufferedBlobModule
|
|
7
|
+
|
|
8
|
+
+ (NSString *)moduleName {
|
|
9
|
+
return @"BufferedBlob";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
13
|
+
return NO;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (NSDictionary *)constantsToExport {
|
|
17
|
+
NSArray<NSString *> *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
|
18
|
+
NSString *docDir = docPaths.firstObject ?: @"";
|
|
19
|
+
|
|
20
|
+
NSArray<NSString *> *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
|
21
|
+
NSString *cacheDir = cachePaths.firstObject ?: @"";
|
|
22
|
+
|
|
23
|
+
NSString *tempDir = NSTemporaryDirectory();
|
|
24
|
+
|
|
25
|
+
NSString *downloadDir = docDir;
|
|
26
|
+
if (@available(iOS 16.0, *)) {
|
|
27
|
+
NSArray<NSString *> *dlPaths = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSUserDomainMask, YES);
|
|
28
|
+
if (dlPaths.firstObject) {
|
|
29
|
+
downloadDir = dlPaths.firstObject;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return @{
|
|
34
|
+
@"documentDir": docDir,
|
|
35
|
+
@"cacheDir": cacheDir,
|
|
36
|
+
@"tempDir": tempDir,
|
|
37
|
+
@"downloadDir": downloadDir,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
42
|
+
#pragma mark - Handle Factories
|
|
43
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
static const NSInteger kMinBufferSize = 4096;
|
|
46
|
+
static const NSInteger kMaxBufferSize = 4194304; // 4MB
|
|
47
|
+
|
|
48
|
+
- (NSNumber *)openRead:(NSString *)path bufferSize:(double)bufferSize {
|
|
49
|
+
NSInteger size = (NSInteger)bufferSize;
|
|
50
|
+
if (size < kMinBufferSize || size > kMaxBufferSize) {
|
|
51
|
+
NSLog(@"[BufferedBlob] openRead: buffer size out of range (%ld). Must be %ld-%ld.",
|
|
52
|
+
(long)size, (long)kMinBufferSize, (long)kMaxBufferSize);
|
|
53
|
+
return @(-1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
NSError *error = nil;
|
|
57
|
+
ReaderHandleIOS *reader = [[ReaderHandleIOS alloc] initWithPath:path
|
|
58
|
+
bufferSize:size
|
|
59
|
+
error:&error];
|
|
60
|
+
if (!reader) {
|
|
61
|
+
return @(-1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
NSInteger handleId = [[HandleRegistry shared] registerObject:reader];
|
|
65
|
+
return @(handleId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
- (NSNumber *)openWrite:(NSString *)path append:(BOOL)append {
|
|
69
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
70
|
+
NSString *parentDir = [path stringByDeletingLastPathComponent];
|
|
71
|
+
if (![fm fileExistsAtPath:parentDir]) {
|
|
72
|
+
[fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
NSError *error = nil;
|
|
76
|
+
WriterHandleIOS *writer = [[WriterHandleIOS alloc] initWithPath:path
|
|
77
|
+
append:append
|
|
78
|
+
error:&error];
|
|
79
|
+
if (!writer) {
|
|
80
|
+
return @(-1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
NSInteger handleId = [[HandleRegistry shared] registerObject:writer];
|
|
84
|
+
return @(handleId);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
- (NSNumber *)createDownload:(NSString *)url destPath:(NSString *)destPath headers:(NSDictionary *)headers {
|
|
88
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
89
|
+
NSString *parentDir = [destPath stringByDeletingLastPathComponent];
|
|
90
|
+
if (![fm fileExistsAtPath:parentDir]) {
|
|
91
|
+
[fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
NSMutableDictionary<NSString *, NSString *> *headerMap = [NSMutableDictionary new];
|
|
95
|
+
[headers enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
|
|
96
|
+
if ([key isKindOfClass:[NSString class]] && [value isKindOfClass:[NSString class]]) {
|
|
97
|
+
headerMap[key] = value;
|
|
98
|
+
}
|
|
99
|
+
}];
|
|
100
|
+
|
|
101
|
+
DownloaderHandleIOS *handle = [[DownloaderHandleIOS alloc] initWithURL:url
|
|
102
|
+
destPath:destPath
|
|
103
|
+
headers:headerMap];
|
|
104
|
+
NSInteger handleId = [[HandleRegistry shared] registerObject:handle];
|
|
105
|
+
return @(handleId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
- (void)closeHandle:(double)handleId {
|
|
109
|
+
[[HandleRegistry shared] removeObjectForId:(NSInteger)handleId];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
113
|
+
#pragma mark - FS Operations
|
|
114
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
- (void)exists:(NSString *)path
|
|
117
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
118
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
119
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
120
|
+
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
|
|
121
|
+
resolve(@(exists));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
- (void)stat:(NSString *)path
|
|
126
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
127
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
128
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
129
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
130
|
+
NSError *error = nil;
|
|
131
|
+
NSDictionary *attrs = [fm attributesOfItemAtPath:path error:&error];
|
|
132
|
+
if (!attrs) {
|
|
133
|
+
reject(@"ERR_FS",
|
|
134
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
|
|
135
|
+
error);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
int64_t size = [attrs[NSFileSize] longLongValue];
|
|
140
|
+
NSDate *modDate = attrs[NSFileModificationDate] ?: [NSDate date];
|
|
141
|
+
NSString *fileType = attrs[NSFileType];
|
|
142
|
+
|
|
143
|
+
NSString *type;
|
|
144
|
+
if ([fileType isEqualToString:NSFileTypeDirectory]) {
|
|
145
|
+
type = @"directory";
|
|
146
|
+
} else if ([fileType isEqualToString:NSFileTypeRegular]) {
|
|
147
|
+
type = @"file";
|
|
148
|
+
} else {
|
|
149
|
+
type = @"unknown";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
NSString *name = [path lastPathComponent];
|
|
153
|
+
resolve(@{
|
|
154
|
+
@"path": path,
|
|
155
|
+
@"name": name,
|
|
156
|
+
@"size": @(size),
|
|
157
|
+
@"type": type,
|
|
158
|
+
@"lastModified": @(modDate.timeIntervalSince1970 * 1000),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
- (void)unlink:(NSString *)path
|
|
164
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
165
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
166
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
167
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
168
|
+
NSError *error = nil;
|
|
169
|
+
if (![fm removeItemAtPath:path error:&error]) {
|
|
170
|
+
// Map "file not found" NSCocoa errors to the same ERR_FS format
|
|
171
|
+
if (error.code == NSFileNoSuchFileError || error.code == NSFileReadNoSuchFileError) {
|
|
172
|
+
reject(@"ERR_FS",
|
|
173
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
|
|
174
|
+
error);
|
|
175
|
+
} else {
|
|
176
|
+
reject(@"ERR_FS", error.localizedDescription, error);
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
resolve(nil);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
- (void)mkdir:(NSString *)path
|
|
185
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
186
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
187
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
188
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
189
|
+
BOOL isDir = NO;
|
|
190
|
+
if ([fm fileExistsAtPath:path isDirectory:&isDir]) {
|
|
191
|
+
if (isDir) {
|
|
192
|
+
resolve(nil);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
reject(@"ERR_FS",
|
|
196
|
+
[NSString stringWithFormat:@"[INVALID_ARGUMENT] Path exists and is not a directory: %@", path],
|
|
197
|
+
nil);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
NSError *error = nil;
|
|
202
|
+
if (![fm createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) {
|
|
203
|
+
reject(@"ERR_FS", error.localizedDescription, error);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
resolve(nil);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
- (void)ls:(NSString *)path
|
|
211
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
212
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
213
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
214
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
215
|
+
BOOL isDir = NO;
|
|
216
|
+
if (![fm fileExistsAtPath:path isDirectory:&isDir]) {
|
|
217
|
+
reject(@"ERR_FS",
|
|
218
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] Directory does not exist: %@", path],
|
|
219
|
+
nil);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!isDir) {
|
|
223
|
+
reject(@"ERR_FS",
|
|
224
|
+
[NSString stringWithFormat:@"[NOT_A_DIRECTORY] Path is not a directory: %@", path],
|
|
225
|
+
nil);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
NSError *error = nil;
|
|
230
|
+
NSArray<NSString *> *contents = [fm contentsOfDirectoryAtPath:path error:&error];
|
|
231
|
+
if (!contents) {
|
|
232
|
+
reject(@"ERR_FS", error.localizedDescription, error);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
NSMutableArray *results = [NSMutableArray new];
|
|
237
|
+
for (NSString *item in contents) {
|
|
238
|
+
NSString *fullPath = [path stringByAppendingPathComponent:item];
|
|
239
|
+
NSDictionary *attrs = [fm attributesOfItemAtPath:fullPath error:nil];
|
|
240
|
+
if (!attrs) continue;
|
|
241
|
+
|
|
242
|
+
int64_t size = [attrs[NSFileSize] longLongValue];
|
|
243
|
+
NSDate *modDate = attrs[NSFileModificationDate] ?: [NSDate date];
|
|
244
|
+
NSString *fileType = attrs[NSFileType];
|
|
245
|
+
|
|
246
|
+
NSString *type;
|
|
247
|
+
if ([fileType isEqualToString:NSFileTypeDirectory]) {
|
|
248
|
+
type = @"directory";
|
|
249
|
+
} else if ([fileType isEqualToString:NSFileTypeRegular]) {
|
|
250
|
+
type = @"file";
|
|
251
|
+
} else {
|
|
252
|
+
type = @"unknown";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[results addObject:@{
|
|
256
|
+
@"path": fullPath,
|
|
257
|
+
@"name": item,
|
|
258
|
+
@"size": @(size),
|
|
259
|
+
@"type": type,
|
|
260
|
+
@"lastModified": @(modDate.timeIntervalSince1970 * 1000),
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
resolve(results);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
- (void)cp:(NSString *)srcPath
|
|
268
|
+
destPath:(NSString *)destPath
|
|
269
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
270
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
271
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
272
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
273
|
+
if (![fm fileExistsAtPath:srcPath]) {
|
|
274
|
+
reject(@"ERR_FS",
|
|
275
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] Source does not exist: %@", srcPath],
|
|
276
|
+
nil);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
NSString *parentDir = [destPath stringByDeletingLastPathComponent];
|
|
281
|
+
if (![fm fileExistsAtPath:parentDir]) {
|
|
282
|
+
[fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if ([fm fileExistsAtPath:destPath]) {
|
|
286
|
+
[fm removeItemAtPath:destPath error:nil];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
NSError *error = nil;
|
|
290
|
+
if (![fm copyItemAtPath:srcPath toPath:destPath error:&error]) {
|
|
291
|
+
reject(@"ERR_FS", error.localizedDescription, error);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
resolve(nil);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
- (void)mv:(NSString *)srcPath
|
|
299
|
+
destPath:(NSString *)destPath
|
|
300
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
301
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
302
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
303
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
304
|
+
if (![fm fileExistsAtPath:srcPath]) {
|
|
305
|
+
reject(@"ERR_FS",
|
|
306
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] Source does not exist: %@", srcPath],
|
|
307
|
+
nil);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
NSString *parentDir = [destPath stringByDeletingLastPathComponent];
|
|
312
|
+
if (![fm fileExistsAtPath:parentDir]) {
|
|
313
|
+
[fm createDirectoryAtPath:parentDir withIntermediateDirectories:YES attributes:nil error:nil];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if ([fm fileExistsAtPath:destPath]) {
|
|
317
|
+
[fm removeItemAtPath:destPath error:nil];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
NSError *error = nil;
|
|
321
|
+
if (![fm moveItemAtPath:srcPath toPath:destPath error:&error]) {
|
|
322
|
+
reject(@"ERR_FS", error.localizedDescription, error);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
resolve(nil);
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
- (void)hashFile:(NSString *)path
|
|
330
|
+
algorithm:(NSString *)algorithm
|
|
331
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
332
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
333
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
334
|
+
NSFileManager *fm = [NSFileManager defaultManager];
|
|
335
|
+
BOOL isDir = NO;
|
|
336
|
+
if ([fm fileExistsAtPath:path isDirectory:&isDir] && isDir) {
|
|
337
|
+
reject(@"ERR_FS",
|
|
338
|
+
[NSString stringWithFormat:@"[INVALID_ARGUMENT] Cannot hash a directory: %@", path],
|
|
339
|
+
nil);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:path];
|
|
344
|
+
if (!inputStream) {
|
|
345
|
+
reject(@"ERR_FS",
|
|
346
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
|
|
347
|
+
nil);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
[inputStream open];
|
|
351
|
+
if (inputStream.streamStatus == NSStreamStatusError) {
|
|
352
|
+
[inputStream close];
|
|
353
|
+
reject(@"ERR_FS",
|
|
354
|
+
[NSString stringWithFormat:@"[FILE_NOT_FOUND] File does not exist: %@", path],
|
|
355
|
+
nil);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const NSUInteger kBufferSize = 8192;
|
|
360
|
+
uint8_t *buffer = malloc(kBufferSize);
|
|
361
|
+
if (!buffer) {
|
|
362
|
+
[inputStream close];
|
|
363
|
+
reject(@"ERR_FS", @"[READ_ERROR] Failed to allocate buffer", nil);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if ([algorithm isEqualToString:@"sha256"]) {
|
|
368
|
+
CC_SHA256_CTX ctx;
|
|
369
|
+
CC_SHA256_Init(&ctx);
|
|
370
|
+
|
|
371
|
+
while ([inputStream hasBytesAvailable]) {
|
|
372
|
+
NSInteger bytesRead = [inputStream read:buffer maxLength:kBufferSize];
|
|
373
|
+
if (bytesRead < 0) {
|
|
374
|
+
free(buffer);
|
|
375
|
+
[inputStream close];
|
|
376
|
+
reject(@"ERR_FS", @"[READ_ERROR] Error reading file", nil);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
if (bytesRead == 0) break;
|
|
380
|
+
CC_SHA256_Update(&ctx, buffer, (CC_LONG)bytesRead);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
|
384
|
+
CC_SHA256_Final(digest, &ctx);
|
|
385
|
+
|
|
386
|
+
free(buffer);
|
|
387
|
+
[inputStream close];
|
|
388
|
+
|
|
389
|
+
NSMutableString *hex = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
|
390
|
+
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
|
391
|
+
[hex appendFormat:@"%02x", digest[i]];
|
|
392
|
+
}
|
|
393
|
+
resolve(hex);
|
|
394
|
+
|
|
395
|
+
} else if ([algorithm isEqualToString:@"md5"]) {
|
|
396
|
+
CC_MD5_CTX ctx;
|
|
397
|
+
CC_MD5_Init(&ctx);
|
|
398
|
+
|
|
399
|
+
while ([inputStream hasBytesAvailable]) {
|
|
400
|
+
NSInteger bytesRead = [inputStream read:buffer maxLength:kBufferSize];
|
|
401
|
+
if (bytesRead < 0) {
|
|
402
|
+
free(buffer);
|
|
403
|
+
[inputStream close];
|
|
404
|
+
reject(@"ERR_FS", @"[READ_ERROR] Error reading file", nil);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (bytesRead == 0) break;
|
|
408
|
+
CC_MD5_Update(&ctx, buffer, (CC_LONG)bytesRead);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
unsigned char digest[CC_MD5_DIGEST_LENGTH];
|
|
412
|
+
CC_MD5_Final(digest, &ctx);
|
|
413
|
+
|
|
414
|
+
free(buffer);
|
|
415
|
+
[inputStream close];
|
|
416
|
+
|
|
417
|
+
NSMutableString *hex = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
|
|
418
|
+
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
|
419
|
+
[hex appendFormat:@"%02x", digest[i]];
|
|
420
|
+
}
|
|
421
|
+
resolve(hex);
|
|
422
|
+
|
|
423
|
+
} else {
|
|
424
|
+
free(buffer);
|
|
425
|
+
[inputStream close];
|
|
426
|
+
reject(@"ERR_FS",
|
|
427
|
+
[NSString stringWithFormat:@"[INVALID_ARGUMENT] Unknown algorithm: %@", algorithm],
|
|
428
|
+
nil);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
@end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
#import <React/RCTBridge+Private.h>
|
|
3
|
+
#import <ReactCommon/RCTTurboModule.h>
|
|
4
|
+
#import <ReactCommon/CallInvoker.h>
|
|
5
|
+
#import <jsi/jsi.h>
|
|
6
|
+
#import "BufferedBlobStreamingBridge.h"
|
|
7
|
+
#import "BufferedBlobModule.h"
|
|
8
|
+
#import "HandleRegistry.h"
|
|
9
|
+
|
|
10
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
11
|
+
#import <BufferedBlobSpec/BufferedBlobSpec.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
@interface BufferedBlobModuleBridge : NSObject <
|
|
15
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
16
|
+
NativeBufferedBlobSpec
|
|
17
|
+
#else
|
|
18
|
+
RCTBridgeModule
|
|
19
|
+
#endif
|
|
20
|
+
>
|
|
21
|
+
@property (nonatomic, strong) BufferedBlobModule *module;
|
|
22
|
+
- (void)setRuntimePointer:(facebook::jsi::Runtime *)runtime;
|
|
23
|
+
@end
|
|
24
|
+
|
|
25
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
26
|
+
namespace facebook::react {
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Subclass the codegen JSI wrapper to intercept install() and capture
|
|
30
|
+
* the jsi::Runtime reference, which is only available inside JSI host functions.
|
|
31
|
+
*/
|
|
32
|
+
class BufferedBlobSpecJSI : public NativeBufferedBlobSpecJSI {
|
|
33
|
+
public:
|
|
34
|
+
BufferedBlobSpecJSI(const ObjCTurboModule::InitParams ¶ms)
|
|
35
|
+
: NativeBufferedBlobSpecJSI(params) {
|
|
36
|
+
// Override install to capture runtime before delegating to ObjC
|
|
37
|
+
methodMap_["install"] = MethodMetadata {0,
|
|
38
|
+
[](jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value *args, size_t count) -> jsi::Value {
|
|
39
|
+
auto &self = static_cast<BufferedBlobSpecJSI &>(turboModule);
|
|
40
|
+
auto *module = static_cast<BufferedBlobModuleBridge *>(self.instance_);
|
|
41
|
+
[module setRuntimePointer:&rt];
|
|
42
|
+
return static_cast<ObjCTurboModule &>(turboModule).invokeObjCMethod(
|
|
43
|
+
rt, BooleanKind, "install", @selector(install), args, count);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} // namespace facebook::react
|
|
50
|
+
#endif
|
|
51
|
+
|
|
52
|
+
@implementation BufferedBlobModuleBridge {
|
|
53
|
+
facebook::jsi::Runtime *_runtime;
|
|
54
|
+
std::shared_ptr<facebook::react::CallInvoker> _callInvoker;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
RCT_EXPORT_MODULE(BufferedBlob)
|
|
58
|
+
|
|
59
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
60
|
+
return NO;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
- (instancetype)init {
|
|
64
|
+
self = [super init];
|
|
65
|
+
if (self) {
|
|
66
|
+
_module = [[BufferedBlobModule alloc] init];
|
|
67
|
+
_runtime = nullptr;
|
|
68
|
+
}
|
|
69
|
+
return self;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (void)setRuntimePointer:(facebook::jsi::Runtime *)runtime {
|
|
73
|
+
_runtime = runtime;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
- (NSDictionary *)constantsToExport {
|
|
77
|
+
return [_module constantsToExport];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
- (NSDictionary *)getConstants {
|
|
81
|
+
return [self constantsToExport];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- install(): Wire JSI HostObject ---
|
|
85
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
86
|
+
@try {
|
|
87
|
+
// New arch path: runtime and callInvoker are captured by
|
|
88
|
+
// getTurboModule: (callInvoker) and the JSI install() override (runtime).
|
|
89
|
+
if (_runtime && _callInvoker) {
|
|
90
|
+
installBufferedBlobStreaming(*_runtime, _callInvoker);
|
|
91
|
+
return @(YES);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Old arch / bridge fallback
|
|
95
|
+
RCTBridge *bridge = self.bridge;
|
|
96
|
+
if (!bridge) {
|
|
97
|
+
bridge = [RCTBridge currentBridge];
|
|
98
|
+
}
|
|
99
|
+
if (!bridge) {
|
|
100
|
+
return @(NO);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
RCTCxxBridge *target = (RCTCxxBridge *)(bridge.batchedBridge ?: bridge);
|
|
104
|
+
|
|
105
|
+
void *runtimePtr = target.runtime;
|
|
106
|
+
if (!runtimePtr) {
|
|
107
|
+
return @(NO);
|
|
108
|
+
}
|
|
109
|
+
facebook::jsi::Runtime *runtime = (facebook::jsi::Runtime *)runtimePtr;
|
|
110
|
+
|
|
111
|
+
auto callInvoker = [bridge jsCallInvoker];
|
|
112
|
+
if (!callInvoker) {
|
|
113
|
+
return @(NO);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_runtime = runtime;
|
|
117
|
+
_callInvoker = callInvoker;
|
|
118
|
+
|
|
119
|
+
installBufferedBlobStreaming(*_runtime, _callInvoker);
|
|
120
|
+
return @(YES);
|
|
121
|
+
} @catch (NSException *exception) {
|
|
122
|
+
NSLog(@"[BufferedBlob] install() failed: %@", exception.reason);
|
|
123
|
+
return @(NO);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
128
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
129
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params {
|
|
130
|
+
_callInvoker = params.jsInvoker;
|
|
131
|
+
return std::make_shared<facebook::react::BufferedBlobSpecJSI>(params);
|
|
132
|
+
}
|
|
133
|
+
#endif
|
|
134
|
+
|
|
135
|
+
// --- Handle Factories ---
|
|
136
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(openRead:(NSString *)path bufferSize:(double)bufferSize) {
|
|
137
|
+
return [_module openRead:path bufferSize:bufferSize];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(openWrite:(NSString *)path append:(BOOL)append) {
|
|
141
|
+
return [_module openWrite:path append:append];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(createDownload:(NSString *)url destPath:(NSString *)destPath headers:(NSDictionary *)headers) {
|
|
145
|
+
return [_module createDownload:url destPath:destPath headers:headers];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(closeHandle:(double)handleId) {
|
|
149
|
+
[_module closeHandle:handleId];
|
|
150
|
+
return nil;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- FS Operations ---
|
|
154
|
+
RCT_EXPORT_METHOD(exists:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
155
|
+
[_module exists:path resolve:resolve reject:reject];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
RCT_EXPORT_METHOD(stat:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
159
|
+
[_module stat:path resolve:resolve reject:reject];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
RCT_EXPORT_METHOD(unlink:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
163
|
+
[_module unlink:path resolve:resolve reject:reject];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
RCT_EXPORT_METHOD(mkdir:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
167
|
+
[_module mkdir:path resolve:resolve reject:reject];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
RCT_EXPORT_METHOD(ls:(NSString *)path resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
171
|
+
[_module ls:path resolve:resolve reject:reject];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
RCT_EXPORT_METHOD(cp:(NSString *)srcPath destPath:(NSString *)destPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
175
|
+
[_module cp:srcPath destPath:destPath resolve:resolve reject:reject];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
RCT_EXPORT_METHOD(mv:(NSString *)srcPath destPath:(NSString *)destPath resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
179
|
+
[_module mv:srcPath destPath:destPath resolve:resolve reject:reject];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
RCT_EXPORT_METHOD(hashFile:(NSString *)path algorithm:(NSString *)algorithm resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
|
|
183
|
+
[_module hashFile:path algorithm:algorithm resolve:resolve reject:reject];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
- (void)invalidate {
|
|
187
|
+
[[HandleRegistry shared] clear];
|
|
188
|
+
_runtime = nullptr;
|
|
189
|
+
_callInvoker = nullptr;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <React/RCTBridgeModule.h>
|
|
4
|
+
#import <ReactCommon/CallInvoker.h>
|
|
5
|
+
#import <jsi/jsi.h>
|
|
6
|
+
|
|
7
|
+
#ifdef __cplusplus
|
|
8
|
+
extern "C" {
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Install the BufferedBlobStreaming JSI HostObject on the given runtime.
|
|
13
|
+
* Called from BufferedBlobModule.mm during install().
|
|
14
|
+
*/
|
|
15
|
+
void installBufferedBlobStreaming(
|
|
16
|
+
facebook::jsi::Runtime& runtime,
|
|
17
|
+
std::shared_ptr<facebook::react::CallInvoker> callInvoker);
|
|
18
|
+
|
|
19
|
+
#ifdef __cplusplus
|
|
20
|
+
}
|
|
21
|
+
#endif
|