Skip to content

Commit f3f095e

Browse files
authored
Merge pull request #440 from ShahilMangroliya/assets-library-fix
Update React Native Blob Util to use Photos framework instead of Asse… Thank you very much!
2 parents 440c2f3 + b91055e commit f3f095e

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)