diff --git a/src/fs.js b/src/fs.js index 34e5955f9..911ff3f11 100644 --- a/src/fs.js +++ b/src/fs.js @@ -173,6 +173,7 @@ function writeFile(path:string, data:string | Array, encoding:?string):P function appendFile(path:string, data:string | Array, encoding:?string):Promise { encoding = encoding || 'utf8' + console.log('fs append file', data, encoding) if(typeof path !== 'string') return Promise.reject('Invalid argument "path" ') if(encoding.toLocaleLowerCase() === 'ascii') { diff --git a/src/ios/RNFetchBlobFS.m b/src/ios/RNFetchBlobFS.m index b97a73e83..cb7aae891 100644 --- a/src/ios/RNFetchBlobFS.m +++ b/src/ios/RNFetchBlobFS.m @@ -213,49 +213,32 @@ + (void) writeFile:(NSString *)path encoding:(NSString *)encoding data:(NSString encoding = [encoding lowercaseString]; if(![fm fileExistsAtPath:folder]) { [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&err]; + [fm createFileAtPath:path contents:nil attributes:nil]; } - // if file exists, write file using encoding - if(![fm fileExistsAtPath:path]) { - if([encoding isEqualToString:@"base64"]){ - NSData * byteData = [[NSData alloc] initWithBase64EncodedString:data options:0]; - [fm createFileAtPath:path contents:byteData attributes:NULL]; - } - // write file from file - else if([encoding isEqualToString:@"uri"]) { - [[self class] writeFileFromFile:data toFile:path append:append]; - resolve([NSNull null]); - return; - } - //TODO: from buffer - // else if ([encoding isEqualToString:@"buffer"]){ - // } - // write data as UTF8 string - else - [fm createFileAtPath:path contents:[data dataUsingEncoding:NSUTF8StringEncoding] attributes:NULL]; + if(err != nil) { + reject(@"RNFetchBlob writeFile Error", @"could not create file at path", path); + return; + } + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; + NSData * content = nil; + if([encoding isEqualToString:@"base64"]) { + content = [[NSData alloc] initWithBase64EncodedString:data options:0]; + } + else if([encoding isEqualToString:@"uri"]) { + [[self class] writeFileFromFile:data toFile:path append:append]; + resolve([NSNull null]); + return; } - // file does not exists, create one else { - NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; - NSData * content = nil; - if([encoding isEqualToString:@"base64"]) { - content = [[NSData alloc] initWithBase64EncodedString:data options:0]; - } - else if([encoding isEqualToString:@"uri"]) { - [[self class] writeFileFromFile:data toFile:path append:append]; - resolve([NSNull null]); - return; - } - else { - content = [data dataUsingEncoding:NSUTF8StringEncoding]; - } - if(append == YES) { - [fileHandle seekToEndOfFile]; - [fileHandle writeData:content]; - [fileHandle closeFile]; - } - else { - [content writeToFile:path atomically:YES]; - } + content = [data dataUsingEncoding:NSUTF8StringEncoding]; + } + if(append == YES) { + [fileHandle seekToEndOfFile]; + [fileHandle writeData:content]; + [fileHandle closeFile]; + } + else { + [content writeToFile:path atomically:YES]; } fm = nil; resolve([NSNull null]); diff --git a/src/ios/RNFetchBlobNetwork.m b/src/ios/RNFetchBlobNetwork.m index bcf327861..e33b729b0 100644 --- a/src/ios/RNFetchBlobNetwork.m +++ b/src/ios/RNFetchBlobNetwork.m @@ -134,8 +134,6 @@ - (void) sendRequest:(NSDictionary * _Nullable )options if(path != nil) destPath = path; - // else - // destPath = [RNFetchBlobFS getTempPath:cacheKey withExtension:[self.options valueForKey:CONFIG_FILE_EXT]]; } else { diff --git a/src/ios/RNFetchBlobReqBuilder.m b/src/ios/RNFetchBlobReqBuilder.m index 794826e88..4ab83915a 100644 --- a/src/ios/RNFetchBlobReqBuilder.m +++ b/src/ios/RNFetchBlobReqBuilder.m @@ -86,7 +86,13 @@ +(void) buildOctetRequest:(NSDictionary *)options if([[method lowercaseString] isEqualToString:@"post"] || [[method lowercaseString] isEqualToString:@"put"]) { // generate octet-stream body if(body != nil) { - + + // when headers does not contain a key named "content-type" (case ignored), use default content type + if([[self class] getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders] == nil) + { + [mheaders setValue:@"application/octet-stream" forKey:@"Content-Type"]; + } + // when body is a string contains file path prefix, try load file from the path if([body hasPrefix:FILE_PREFIX]) { NSString * orgPath = [body substringFromIndex:[FILE_PREFIX length]]; @@ -95,10 +101,6 @@ +(void) buildOctetRequest:(NSDictionary *)options { [RNFetchBlobFS readFile:orgPath encoding:@"utf8" resolver:nil rejecter:nil onComplete:^(NSData *content) { [request setHTTPBody:content]; - if([mheaders valueForKey:@"content-type"] == nil) - { - [mheaders setValue:@"application/octet-stream" forKey:@"content-type"]; - } [request setHTTPMethod: method]; [request setAllHTTPHeaderFields:mheaders]; onComplete(request, [content length]); @@ -110,24 +112,22 @@ +(void) buildOctetRequest:(NSDictionary *)options } // otherwise convert it as BASE64 data string else { - // the body is BASE64 encoded string - if(([mheaders valueForKey:@"content-type"] == nil && [mheaders valueForKey:@"Content-Type"] == nil) || - ([[[mheaders valueForKey:@"content-type"] lowercaseString] isEqualToString:@"application/octet-stream"] || - [[[mheaders valueForKey:@"Content-Type"] lowercaseString] isEqualToString:@"application/octet-stream"])) + + NSString * cType = [[self class]getHeaderIgnoreCases:@"content-type" fromHeaders:mheaders]; + // when content-type is application/octet* decode body string using BASE64 decoder + if([[cType lowercaseString] hasPrefix:@"application/octet"]) { blobData = [[NSData alloc] initWithBase64EncodedString:body options:0]; - [mheaders setValue:@"application/octet-stream" forKey:@"Content-Type"]; [request setHTTPBody:blobData]; size = [blobData length]; } - // use the body string as is + // otherwise use the body as-is else { size = [body length]; [request setHTTPBody: [body dataUsingEncoding:NSUTF8StringEncoding]]; } } - } } @@ -138,21 +138,6 @@ +(void) buildOctetRequest:(NSDictionary *)options }); } -//+(void) buildEncodedRequest:(NSDictionary *)options -// taskId:(NSString *)taskId -// method:(NSString *)method -// url:(NSString *)url -// headers:(NSDictionary *)headers -// body:(NSString *)body -// onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete -//{ -// NSMutableData * formData = [[NSMutableData alloc] init]; -// -// [formData appendData:[[NSString stringWithFormat:@"%@", body] dataUsingEncoding:NSUTF8StringEncoding]]; -// onComplete(formData); -//} - - +(void) buildFormBody:(NSArray *)form boundary:(NSString *)boundary onComplete:(void(^)(NSData * formData))onComplete { NSMutableData * formData = [[NSMutableData alloc] init]; @@ -225,5 +210,16 @@ void __block (^getFieldData)(id field) = ^(id field) } } ++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers { + + NSString * normalCase = [headers valueForKey:field]; + NSString * ignoredCase = [headers valueForKey:[field lowercaseString]]; + if( normalCase != nil) + return normalCase; + else + return ignoredCase; + +} + @end diff --git a/src/polyfill/Blob.js b/src/polyfill/Blob.js index a52d2b38e..a17044c1d 100644 --- a/src/polyfill/Blob.js +++ b/src/polyfill/Blob.js @@ -25,7 +25,7 @@ export default class Blob { _ref:string = null; _blobCreated:boolean = false; - _onCreated:() => void; + _onCreated:Array = []; static Instances:any = {} @@ -35,86 +35,44 @@ export default class Blob { this.cacheName = getBlobName() this.isRNFetchBlobPolyfill = true this.type = mime - log.verbose('Blob constructor called' , data, 'mime', mime) - - if(typeof data === 'string') { - // content from file - if(data.startsWith('RNFetchBlob-file://')) { - this._ref = data - this._blobCreated = true - if(typeof this._onCreated === 'function') - this._onCreated(this) - } - // content from variable need create file - else { - log.verbose('create Blob cache file ..') - this._ref = RNFetchBlob.wrap(blobCacheDir + this.cacheName) - let encoding = 'utf8' - if(typeof data === 'string' && String(mime).match('application/octet') ) - encoding = 'base64' - else if(Array.isArray(data)) - encoding = 'ascii' - - this.init(data, encoding) - .then(() => { - log.verbose('init executed ') - if(typeof this._onCreated === 'function') - this._onCreated(this) - }) - .catch((err) => { - log.error('RNFetchBlob cannot create Blob', err) - }) - } + log.verbose('Blob constructor called', 'mime', mime) + this._ref = blobCacheDir + this.cacheName + let p = null + // content from file + if(typeof data === 'string' && data.startsWith('RNFetchBlob-file://')) { + log.verbose('create Blob cache file from file path') + this._ref = data + p = Promise.resolve() } - // TODO : handle mixed blob array + // content from variable need create file + else if(typeof data === 'string') { + log.verbose('create Blob cache file from string') + let encoding = 'utf8' + if(String(mime).match('application/octet')) + encoding = 'base64' + else if(Array.isArray(data)) + encoding = 'ascii' + // create cache file + p = fs.writeFile(this._ref, data, encoding) + + } + // when input is an array of mixed data types, create a file cache else if(Array.isArray(data)) { - this._ref = RNFetchBlob.wrap(blobCacheDir + this.cacheName) - createMixedBlobData(this._ref, data) - .then(() => { - if(typeof this._onCreated === 'function') - this._onCreated(this) - }) + log.verbose('create Blob cache file from mixed array', data) + p = createMixedBlobData(this._ref, data) } + p && p.then(() => { + this._invokeOnCreateEvent() + }) + .catch((err) => { + log.error('RNFetchBlob cannot create Blob : '+ this._ref) + }) } onCreated(fn:() => void) { - log.verbose('register blob onCreated') - if(this._blobCreated) - fn() - else - this._onCreated = fn - } - - /** - * Create blob file cache - * @nonstandard - * @param {string | Array} data Data to create Blob file - * @param {'base64' | 'utf8' | 'ascii'} encoding RNFetchBlob fs encoding - * @return {Promise} - */ - init(data, encoding):Promise { - return new Promise((resolve, reject) => { - fs.exists(blobCacheDir) - .then((exist) => { - log.verbose('blob cache folder exist', blobCacheDir, exist) - let path = String(this._ref).replace('RNFetchBlob-file://', '') - log.verbose('create cache file', path) - if(!exist) - return fs.mkdir(blobCacheDir) - .then(() => fs.createFile(path, data, encoding)) - else - return fs.createFile(path, data, encoding) - }) - .then(() => { - this._blobCreated = true - resolve() - }) - .catch((err) => { - reject(err) - }) - }) - + log.verbose('register blob onCreated', this._onCreated.length) + this._onCreated.push(fn) } /** @@ -146,6 +104,21 @@ export default class Blob { return fs.unlink(this._ref) } + clearCache() { + + } + + _invokeOnCreateEvent() { + log.verbose('invoke create event') + this._blobCreated = true + let fns = this._onCreated + for(let i in fns) { + if(typeof fns[i] === 'function') + fns[i](this) + } + delete this._onCreated + } + } /** @@ -164,15 +137,21 @@ function getBlobName() { * @return {Promise} */ function createMixedBlobData(ref, dataArray) { - let p = fs.createFile(ref, '') + let p = fs.writeFile(ref, '') + let args = [] for(let i in dataArray) { let part = dataArray[i] if(part instanceof Blob) - p.then(() => fs.appendFile(ref, part.getRNFetchBlobRef()), 'uri') + args.push([ref, part.getRNFetchBlobRef(), 'uri']) + else if(typeof part === 'string') + args.push([ref, part, 'utf8']) else if (Array.isArray(part)) - p.then(() => fs.appendFile(ref), part, 'ascii') - else - p.then(() => fs.appendFile(ref), part, 'utf8') + args.push([ref, part, 'ascii']) } - return p + return p.then(() => { + let promises = args.map((p) => { + return fs.appendFile.call(this, ...p) + }) + return Promise.all(promises) + }) } diff --git a/src/polyfill/XMLHttpRequest.js b/src/polyfill/XMLHttpRequest.js index 9f7c2a75e..cf01fe32b 100644 --- a/src/polyfill/XMLHttpRequest.js +++ b/src/polyfill/XMLHttpRequest.js @@ -92,8 +92,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ this._upload = new XMLHttpRequestEventTarget() log.verbose(typeof body, body instanceof FormData) - if(Array.isArray(body)) { - // TODO + + if(body instanceof Blob) { + body = RNFetchBlob.wrap(body.getRNFetchBlobRef()) } this.dispatchEvent('loadstart') @@ -211,7 +212,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{ } _onDone(resp) { - log.verbose('XMLHttpRequest done', resp.text()) + log.verbose('XMLHttpRequest done', this) this.statusText = '200 OK' this._status = 200 switch(resp.type) {