Skip to content

Commit b91055e

Browse files
Update React Native Blob Util to use Photos framework instead of AssetsLibrary for iOS. Refactor asset handling methods to accommodate PHAsset, including changes to data retrieval and metadata extraction.
1 parent 440c2f3 commit b91055e

File tree

4 files changed

+142
-107
lines changed

4 files changed

+142
-107
lines changed

ios/ReactNativeBlobUtil/ReactNativeBlobUtil.mm

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ - (void)ls:(NSString *)path
512512
RCT_EXPORT_METHOD(stat:(NSString *)target callback:(RCTResponseSenderBlock) callback)
513513
{
514514

515-
[ReactNativeBlobUtilFS getPathFromUri:target completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
515+
[ReactNativeBlobUtilFS getPathFromUri:target completionHandler:^(NSString *path, PHAsset *asset) {
516516
__block NSMutableDictionary * result;
517517
if(path != nil)
518518
{
@@ -536,10 +536,29 @@ - (void)ls:(NSString *)path
536536
}
537537
else if(asset != nil)
538538
{
539-
__block NSNumber * size = [NSNumber numberWithLong:[asset size]];
540-
result = [asset metadata];
541-
[result setValue:size forKey:@"size"];
542-
callback(@[[NSNull null], result]);
539+
// For PHAsset, get basic metadata
540+
NSMutableDictionary *assetInfo = [NSMutableDictionary dictionary];
541+
[assetInfo setValue:[NSNumber numberWithLong:asset.pixelWidth] forKey:@"width"];
542+
[assetInfo setValue:[NSNumber numberWithLong:asset.pixelHeight] forKey:@"height"];
543+
[assetInfo setValue:[NSNumber numberWithLong:asset.duration] forKey:@"duration"];
544+
[assetInfo setValue:[NSDate date] forKey:@"lastModified"];
545+
[assetInfo setValue:@"asset" forKey:@"type"];
546+
[assetInfo setValue:@"PHAsset" forKey:@"filename"];
547+
548+
// Get actual file size by requesting image data
549+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
550+
options.synchronous = YES;
551+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
552+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
553+
554+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
555+
if (imageData) {
556+
[assetInfo setValue:[NSNumber numberWithLong:imageData.length] forKey:@"size"];
557+
} else {
558+
[assetInfo setValue:[NSNumber numberWithLong:0] forKey:@"size"];
559+
}
560+
callback(@[[NSNull null], assetInfo]);
561+
}];
543562
}
544563
else
545564
{
@@ -597,7 +616,7 @@ - (void)cp:(NSString *)src
597616
callback:(RCTResponseSenderBlock)callback
598617
{
599618
// path = [ReactNativeBlobUtilFS getPathOfAsset:path];
600-
[ReactNativeBlobUtilFS getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
619+
[ReactNativeBlobUtilFS getPathFromUri:src completionHandler:^(NSString *path, PHAsset *asset) {
601620
NSError * error = nil;
602621
if(path == nil)
603622
{

ios/ReactNativeBlobUtilFS.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#import "RCTBridgeModule.h"
1919
#endif
2020

21-
#import <AssetsLibrary/AssetsLibrary.h>
21+
#import <Photos/Photos.h>
2222

2323
@interface ReactNativeBlobUtilFS : NSObject <NSStreamDelegate> {
2424
NSOutputStream * outStream;
@@ -57,7 +57,7 @@
5757
+ (NSString *) getTempPath:(NSString*)taskId withExtension:(NSString *)ext;
5858
+ (NSString *) getPathOfAsset:(NSString *)assetURI;
5959
+ (NSString *) getPathForAppGroup:(NSString *)groupName;
60-
+ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete;
60+
+ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, PHAsset *asset)) onComplete;
6161

6262
// fs methods
6363
+ (ReactNativeBlobUtilFS *) getFileStreams;
@@ -82,7 +82,7 @@
8282
resolver:(RCTPromiseResolveBlock)resolve
8383
rejecter:(RCTPromiseRejectBlock)reject;
8484
//+ (void) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append;
85-
+ (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest;
85+
+ (void) writeAssetToPath:(PHAsset * )asset dest:(NSString *)dest;
8686
+ (void) readStream:(NSString *)uri encoding:(NSString * )encoding bufferSize:(int)bufferSize tick:(int)tick streamId:(NSString *)streamId baseModule:(ReactNativeBlobUtil *)baseModule;
8787
+ (void) df:(RCTResponseSenderBlock)callback;
8888

ios/ReactNativeBlobUtilFS.mm

Lines changed: 113 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#import "ReactNativeBlobUtilFS.h"
1212
#import "ReactNativeBlobUtilConst.h"
1313
#import "ReactNativeBlobUtilFileTransformer.h"
14-
#import <AssetsLibrary/AssetsLibrary.h>
14+
#import <Photos/Photos.h>
1515

1616
#import <CommonCrypto/CommonDigest.h>
1717

@@ -154,7 +154,7 @@ + (void) readStream:(NSString *)uri
154154
streamId:(NSString *)streamId
155155
baseModule:(ReactNativeBlobUtil*)baseModule
156156
{
157-
[[self class] getPathFromUri:uri completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
157+
[[self class] getPathFromUri:uri completionHandler:^(NSString *path, PHAsset *asset) {
158158

159159
__block int read = 0;
160160
__block int backoff = tick *1000;
@@ -188,17 +188,34 @@ + (void) readStream:(NSString *)uri
188188
}
189189
else if (asset != nil)
190190
{
191-
int cursor = 0;
192-
NSError * err;
193-
while((read = [asset getBytes:buffer fromOffset:cursor length:bufferSize error:&err]) > 0)
194-
{
195-
cursor += read;
196-
[[self class] emitDataChunks:[NSData dataWithBytes:buffer length:read] encoding:encoding streamId:streamId baseModule:baseModule];
197-
if(tick > 0)
198-
{
199-
usleep(backoff);
200-
}
201-
}
191+
// For PHAsset, we need to get the full image data first
192+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
193+
options.synchronous = YES;
194+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
195+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
196+
197+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
198+
if (imageData) {
199+
// Write to temporary file and then read it back in chunks
200+
NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"temp_asset_%@.tmp", [[NSUUID UUID] UUIDString]]];
201+
[imageData writeToFile:tempPath atomically:YES];
202+
203+
NSInputStream *stream = [[NSInputStream alloc] initWithFileAtPath:tempPath];
204+
[stream open];
205+
while((read = [stream read:buffer maxLength:bufferSize]) > 0)
206+
{
207+
[[self class] emitDataChunks:[NSData dataWithBytes:buffer length:read] encoding:encoding streamId:streamId baseModule:baseModule];
208+
if(tick > 0)
209+
{
210+
usleep(backoff);
211+
}
212+
}
213+
[stream close];
214+
215+
// Clean up temp file
216+
[[NSFileManager defaultManager] removeItemAtPath:tempPath error:nil];
217+
}
218+
}];
202219
}
203220
else
204221
{
@@ -290,7 +307,7 @@ + (void) emitDataChunks:(NSData *)data encoding:(NSString *) encoding streamId:(
290307

291308
+ (NSNumber *) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:(BOOL)append callback:(void(^)(NSString * errMsg, NSNumber *size))callback
292309
{
293-
[[self class] getPathFromUri:src completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
310+
[[self class] getPathFromUri:src completionHandler:^(NSString *path, PHAsset *asset) {
294311
if(path != nil)
295312
{
296313
__block NSInputStream * is = [[NSInputStream alloc] initWithFileAtPath:path];
@@ -313,21 +330,24 @@ + (NSNumber *) writeFileFromFile:(NSString *)src toFile:(NSString *)dest append:
313330
}
314331
else if(asset != nil)
315332
{
316-
317-
__block NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:append];
318-
int read = 0;
319-
int cursor = 0;
320-
__block long written = 0;
321-
uint8_t buffer[10240];
322-
[os open];
323-
while((read = [asset getBytes:buffer fromOffset:cursor length:10240 error:nil]) > 0)
324-
{
325-
cursor += read;
326-
[os write:buffer maxLength:read];
327-
}
328-
__block NSNumber * size = [NSNumber numberWithLong:written];
329-
[os close];
330-
callback(nil, size);
333+
// For PHAsset, get the full image data and write it
334+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
335+
options.synchronous = YES;
336+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
337+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
338+
339+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
340+
if (imageData) {
341+
NSOutputStream *os = [[NSOutputStream alloc] initToFileAtPath:dest append:append];
342+
[os open];
343+
[imageData writeToFile:dest atomically:YES];
344+
NSNumber *size = [NSNumber numberWithLong:imageData.length];
345+
[os close];
346+
callback(nil, size);
347+
} else {
348+
callback(@"Failed to get image data from asset", nil);
349+
}
350+
}];
331351
}
332352
else
333353
callback(@"failed to resolve path", nil);
@@ -496,23 +516,26 @@ + (void) readFile:(NSString *)path
496516
transformFile:(BOOL) transformFile
497517
onComplete:(void (^)(NSData * content, NSString * codeStr, NSString * errMsg))onComplete
498518
{
499-
[[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
519+
[[self class] getPathFromUri:path completionHandler:^(NSString *path, PHAsset *asset) {
500520
__block NSData * fileContent;
501521
NSError * err;
502522
__block Byte * buffer;
503523
if(asset != nil)
504524
{
505-
int size = asset.size;
506-
buffer = (Byte *)malloc(size);
507-
[asset getBytes:buffer fromOffset:0 length:asset.size error:&err];
508-
if(err != nil)
509-
{
510-
onComplete(nil, @"EUNSPECIFIED", [err description]);
511-
free(buffer);
512-
return;
513-
}
514-
fileContent = [NSData dataWithBytes:buffer length:size];
515-
free(buffer);
525+
// For PHAsset, get the full image data
526+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
527+
options.synchronous = YES;
528+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
529+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
530+
531+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
532+
if (imageData) {
533+
onComplete(imageData, nil, nil);
534+
} else {
535+
onComplete(nil, @"EUNSPECIFIED", @"Failed to get image data from asset");
536+
}
537+
}];
538+
return;
516539
}
517540
else
518541
{
@@ -795,7 +818,7 @@ + (NSDictionary *) stat:(NSString *) path error:(NSError **) error {
795818

796819
+ (void) exists:(NSString *) path callback:(RCTResponseSenderBlock)callback
797820
{
798-
[[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset) {
821+
[[self class] getPathFromUri:path completionHandler:^(NSString *path, PHAsset *asset) {
799822
if(path != nil)
800823
{
801824
BOOL isDir = NO;
@@ -886,7 +909,7 @@ + (void)slice:(NSString *)path
886909
resolver:(RCTPromiseResolveBlock)resolve
887910
rejecter:(RCTPromiseRejectBlock)reject
888911
{
889-
[[self class] getPathFromUri:path completionHandler:^(NSString *path, ALAssetRepresentation *asset)
912+
[[self class] getPathFromUri:path completionHandler:^(NSString *path, PHAsset *asset)
890913
{
891914
if(path != nil)
892915
{
@@ -945,39 +968,34 @@ + (void)slice:(NSString *)path
945968
}
946969
else if (asset != nil)
947970
{
948-
long expected = [end longValue] - [start longValue];
949-
long read = 0;
950-
long chunkRead = 0;
951-
NSOutputStream * os = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
952-
[os open];
953-
long size = asset.size;
954-
long max = MIN(size, [end longValue]);
955-
956-
while(read < expected) {
957-
uint8_t chunk[10240];
958-
uint8_t * pointerToChunk = &chunk[0];
959-
long chunkSize = 0;
960-
if([start longValue] + read + 10240 > max)
961-
{
962-
NSLog(@"read chunk %lu", max - read - [start longValue]);
963-
chunkSize = max - read - [start longValue];
964-
chunkRead = [asset getBytes:pointerToChunk fromOffset:[start longValue] + read length:chunkSize error:nil];
965-
}
966-
else
967-
{
968-
NSLog(@"read chunk %lu", 10240);
969-
chunkSize = 10240;
970-
chunkRead = [asset getBytes:pointerToChunk fromOffset:[start longValue] + read length:chunkSize error:nil];
971+
// For PHAsset, get the full image data and then slice it
972+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
973+
options.synchronous = YES;
974+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
975+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
976+
977+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
978+
if (imageData) {
979+
long startPos = [start longValue];
980+
long endPos = [end longValue];
981+
long dataLength = imageData.length;
982+
983+
// Clamp the range to the actual data length
984+
startPos = MAX(0, MIN(startPos, dataLength));
985+
endPos = MAX(startPos, MIN(endPos, dataLength));
986+
987+
NSRange range = NSMakeRange(startPos, endPos - startPos);
988+
NSData *sliceData = [imageData subdataWithRange:range];
989+
990+
if ([sliceData writeToFile:dest atomically:YES]) {
991+
resolve(dest);
992+
} else {
993+
reject(@"EUNSPECIFIED", @"Failed to write slice to file", nil);
994+
}
995+
} else {
996+
reject(@"EUNSPECIFIED", @"Failed to get image data from asset", nil);
971997
}
972-
if( chunkRead <= 0)
973-
break;
974-
long remain = expected - read;
975-
976-
[os write:chunk maxLength:chunkSize];
977-
read += chunkRead;
978-
}
979-
[os close];
980-
resolve(dest);
998+
}];
981999
}
9821000
else {
9831001
reject(@"EINVAL", [NSString stringWithFormat: @"Could not resolve URI %@", path ], nil);
@@ -1001,20 +1019,18 @@ - (void)closeInStream
10011019

10021020
# pragma mark - get absolute path of resource
10031021

1004-
+ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, ALAssetRepresentation *asset)) onComplete
1022+
+ (void) getPathFromUri:(NSString *)uri completionHandler:(void(^)(NSString * path, PHAsset *asset)) onComplete
10051023
{
10061024
if([uri hasPrefix:AL_PREFIX])
10071025
{
10081026
NSURL *asseturl = [NSURL URLWithString:uri];
1009-
__block ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
1010-
[assetslibrary assetForURL:asseturl
1011-
resultBlock:^(ALAsset *asset) {
1012-
__block ALAssetRepresentation * present = [asset defaultRepresentation];
1013-
onComplete(nil, present);
1014-
}
1015-
failureBlock:^(NSError *error) {
1016-
onComplete(nil, nil);
1017-
}];
1027+
PHFetchResult<PHAsset *> *assets = [PHAsset fetchAssetsWithALAssetURLs:@[asseturl] options:nil];
1028+
if (assets.count > 0) {
1029+
PHAsset *asset = assets.firstObject;
1030+
onComplete(nil, asset);
1031+
} else {
1032+
onComplete(nil, nil);
1033+
}
10181034
}
10191035
else
10201036
{
@@ -1044,20 +1060,20 @@ +(void) df:(RCTResponseSenderBlock)callback
10441060

10451061
}
10461062

1047-
+ (void) writeAssetToPath:(ALAssetRepresentation * )rep dest:(NSString *)dest
1063+
+ (void) writeAssetToPath:(PHAsset * )asset dest:(NSString *)dest
10481064
{
1049-
int read = 0;
1050-
int cursor = 0;
1051-
Byte * buffer = (Byte *)malloc(10240);
1052-
NSOutputStream * ostream = [[NSOutputStream alloc] initToFileAtPath:dest append:NO];
1053-
[ostream open];
1054-
while((read = [rep getBytes:buffer fromOffset:cursor length:10240 error:nil]) > 0)
1055-
{
1056-
cursor+=10240;
1057-
[ostream write:buffer maxLength:read];
1065+
if (asset.mediaType == PHAssetMediaTypeImage) {
1066+
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
1067+
options.synchronous = YES;
1068+
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
1069+
options.resizeMode = PHImageRequestOptionsResizeModeNone;
1070+
1071+
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
1072+
if (imageData) {
1073+
[imageData writeToFile:dest atomically:YES];
1074+
}
1075+
}];
10581076
}
1059-
[ostream close];
1060-
free(buffer);
10611077
return;
10621078
}
10631079

react-native-blob-util.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Pod::Spec.new do |s|
1717
'ReactNativeBlobUtilPrivacyInfo' => ['ios/PrivacyInfo.xcprivacy'],
1818
}
1919
s.platforms = { :ios => "11.0" }
20-
s.framework = 'AssetsLibrary'
20+
s.framework = 'Photos'
2121

2222
if fabric_enabled
2323
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.

0 commit comments

Comments
 (0)