From d7fb9b8d9e3b9ba330fa6265b2179dcfa674af0d Mon Sep 17 00:00:00 2001 From: Pedro Teran Gezn Date: Wed, 20 Mar 2019 05:18:48 -0400 Subject: [PATCH] fix(http): ensure httpcontent.toFile() creates intermediate directories (#6451) --- tests/app/http/http-tests.ts | 28 +++++-- tests/package.json | 4 +- .../http/http-request/http-request.android.ts | 80 +++++++------------ .../http/http-request/http-request.ios.ts | 78 +++++++++--------- 4 files changed, 92 insertions(+), 98 deletions(-) diff --git a/tests/app/http/http-tests.ts b/tests/app/http/http-tests.ts index a1f257f66..0eb045721 100644 --- a/tests/app/http/http-tests.ts +++ b/tests/app/http/http-tests.ts @@ -158,9 +158,9 @@ export var test_getJSON_fail_when_result_is_not_JSONP = function (done) { export var test_gzip_request_explicit = function(done) { var result; - http.request({ - url: "https://postman-echo.com/gzip", - method: "GET", + http.request({ + url: "https://postman-echo.com/gzip", + method: "GET", headers: { "Accept-Encoding": "gzip" }}).then(function (r) { @@ -180,8 +180,8 @@ export var test_gzip_request_explicit = function(done) { export var test_gzip_request_implicit = function(done) { var result; - http.request({ - url: "https://postman-echo.com/gzip", + http.request({ + url: "https://postman-echo.com/gzip", method: "GET"}).then(function (r) { result = r; try { @@ -524,6 +524,24 @@ export var test_request_responseContentToFileFromUrlShouldReturnCorrectFile = fu done(e); }); }; +export var test_request_responseContentToFileFromUrlShouldReturnCorrectFileAndCreateDirPathIfNecesary = function (done) { + var result; + + http.request({ url: "https://raw.githubusercontent.com/NativeScript/NativeScript/master/tests/app/logo.png", method: "GET" }).then(function (response) { + const filePath = fs.path.join(fs.knownFolders.temp().path, "test", "some", "path", "logo.png"); + result = response.content.toFile(filePath); + try { + TKUnit.assert(result instanceof fs.File, "Result from toFile() should be valid File object!"); + TKUnit.assert(result.size > 0, "result from to file should be greater than 0 in size"); + done(null); + } + catch (err) { + done(err); + } + }, function (e) { + done(e); + }); +}; export var test_request_responseContentToFileFromContentShouldReturnCorrectFile = function (done) { var result; diff --git a/tests/package.json b/tests/package.json index a36530bc3..b821ee2b6 100644 --- a/tests/package.json +++ b/tests/package.json @@ -6,10 +6,10 @@ "nativescript": { "id": "org.nativescript.UnitTestApp", "tns-ios": { - "version": "5.1.0" + "version": "5.2.0" }, "tns-android": { - "version": "5.1.0" + "version": "5.2.1" } }, "dependencies": { diff --git a/tns-core-modules/http/http-request/http-request.android.ts b/tns-core-modules/http/http-request/http-request.android.ts index ff986956f..a6a1f04bc 100644 --- a/tns-core-modules/http/http-request/http-request.android.ts +++ b/tns-core-modules/http/http-request/http-request.android.ts @@ -1,9 +1,9 @@ /** * Android specific http request implementation. */ -import * as imageSourceModule from "../../image-source"; -import * as platformModule from "../../platform"; -import * as fsModule from "../../file-system"; +import { fromNativeSource } from "../../image-source"; +import { screen } from "../../platform"; +import { File } from "../../file-system"; import { getFilenameFromUrl } from "./http-request-common"; // this is imported for definition purposes only @@ -17,7 +17,7 @@ export enum HttpResponseEncoding { } function parseJSON(source: string): any { - var src = source.trim(); + const src = source.trim(); if (src.lastIndexOf(")") === src.length - 1) { return JSON.parse(src.substring(src.indexOf("(") + 1, src.lastIndexOf(")"))); } @@ -25,24 +25,10 @@ function parseJSON(source: string): any { return JSON.parse(src); } -var requestIdCounter = 0; -var pendingRequests = {}; +let requestIdCounter = 0; +const pendingRequests = {}; -var imageSource: typeof imageSourceModule; -function ensureImageSource() { - if (!imageSource) { - imageSource = require("image-source"); - } -} - -var platform: typeof platformModule; -function ensurePlatform() { - if (!platform) { - platform = require("platform"); - } -} - -var completeCallback: org.nativescript.widgets.Async.CompleteCallback; +let completeCallback: org.nativescript.widgets.Async.CompleteCallback; function ensureCompleteCallback() { if (completeCallback) { return; @@ -60,7 +46,7 @@ function ensureCompleteCallback() { } function onRequestComplete(requestId: number, result: org.nativescript.widgets.Async.Http.RequestResult) { - var callbacks = pendingRequests[requestId]; + const callbacks = pendingRequests[requestId]; delete pendingRequests[requestId]; if (result.error) { @@ -69,13 +55,12 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A } // read the headers - var headers: http.Headers = {}; + const headers: http.Headers = {}; if (result.headers) { - var jHeaders = result.headers; - var length = jHeaders.size(); - var i; - var pair: org.nativescript.widgets.Async.Http.KeyValuePair; - for (i = 0; i < length; i++) { + const jHeaders = result.headers; + const length = jHeaders.size(); + let pair: org.nativescript.widgets.Async.Http.KeyValuePair; + for (let i = 0; i < length; i++) { pair = jHeaders.get(i); addHeader(headers, pair.key, pair.value); } @@ -112,11 +97,9 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A return parseJSON(str); }, toImage: () => { - ensureImageSource(); - return new Promise((resolveImage, rejectImage) => { if (result.responseAsImage != null) { - resolveImage(imageSource.fromNativeSource(result.responseAsImage)); + resolveImage(fromNativeSource(result.responseAsImage)); } else { rejectImage(new Error("Response content may not be converted to an Image")); @@ -124,17 +107,19 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A }); }, toFile: (destinationFilePath: string) => { - var fs: typeof fsModule = require("file-system"); - if (!destinationFilePath) { destinationFilePath = getFilenameFromUrl(callbacks.url); } - var stream: java.io.FileOutputStream; + let stream: java.io.FileOutputStream; try { - var javaFile = new java.io.File(destinationFilePath); + // ensure destination path exists by creating any missing parent directories + const file = File.fromPath(destinationFilePath); + + const javaFile = new java.io.File(destinationFilePath); stream = new java.io.FileOutputStream(javaFile); stream.write(result.raw.toByteArray()); - return fs.File.fromPath(destinationFilePath); + + return file; } catch (exception) { throw new Error(`Cannot save file with path: ${destinationFilePath}.`); @@ -152,7 +137,7 @@ function onRequestComplete(requestId: number, result: org.nativescript.widgets.A } function onRequestError(error: string, requestId: number) { - var callbacks = pendingRequests[requestId]; + const callbacks = pendingRequests[requestId]; delete pendingRequests[requestId]; if (callbacks) { callbacks.rejectCallback(new Error(error)); @@ -164,7 +149,7 @@ function buildJavaOptions(options: http.HttpRequestOptions) { throw new Error("Http request must provide a valid url."); } - var javaOptions = new org.nativescript.widgets.Async.Http.RequestOptions(); + const javaOptions = new org.nativescript.widgets.Async.Http.RequestOptions(); javaOptions.url = options.url; @@ -182,22 +167,19 @@ function buildJavaOptions(options: http.HttpRequestOptions) { } if (options.headers) { - var arrayList = new java.util.ArrayList(); - var pair = org.nativescript.widgets.Async.Http.KeyValuePair; + const arrayList = new java.util.ArrayList(); + const pair = org.nativescript.widgets.Async.Http.KeyValuePair; - for (var key in options.headers) { + for (let key in options.headers) { arrayList.add(new pair(key, options.headers[key] + "")); } javaOptions.headers = arrayList; } - ensurePlatform(); - // pass the maximum available image size to the request options in case we need a bitmap conversion - var screen = platform.screen.mainScreen; - javaOptions.screenWidth = screen.widthPixels; - javaOptions.screenHeight = screen.heightPixels; + javaOptions.screenWidth = screen.mainScreen.widthPixels; + javaOptions.screenHeight = screen.mainScreen.heightPixels; return javaOptions; } @@ -211,7 +193,7 @@ export function request(options: http.HttpRequestOptions): Promise((resolve, reject) => { try { // initialize the options - var javaOptions = buildJavaOptions(options); + const javaOptions = buildJavaOptions(options); // send request data to network debugger if (global.__inspector && global.__inspector.isConnected) { @@ -219,7 +201,7 @@ export function request(options: http.HttpRequestOptions): Promiseheaders[key]).push(value); } else { - let values: string[] = [headers[key]]; + const values: string[] = [headers[key]]; values.push(value); headers[key] = values; } diff --git a/tns-core-modules/http/http-request/http-request.ios.ts b/tns-core-modules/http/http-request/http-request.ios.ts index 5b9b6716d..6e02247b2 100644 --- a/tns-core-modules/http/http-request/http-request.ios.ts +++ b/tns-core-modules/http/http-request/http-request.ios.ts @@ -2,10 +2,10 @@ * iOS specific http request implementation. */ -import * as http from "../../http"; +import { HttpRequestOptions, HttpResponse, Headers } from "../../http"; import * as types from "../../utils/types"; -import * as imageSourceModule from "../../image-source"; -import * as fsModule from "../../file-system"; +import { fromNativeSource } from "../../image-source"; +import { File } from "../../file-system"; import * as utils from "../../utils/utils"; import getter = utils.ios.getter; @@ -18,18 +18,18 @@ export enum HttpResponseEncoding { GBK } -var currentDevice = utils.ios.getter(UIDevice, UIDevice.currentDevice); -var device = currentDevice.userInterfaceIdiom === UIUserInterfaceIdiom.Phone ? "Phone" : "Pad"; -var osVersion = currentDevice.systemVersion; +const currentDevice = getter(UIDevice, UIDevice.currentDevice); +const device = currentDevice.userInterfaceIdiom === UIUserInterfaceIdiom.Phone ? "Phone" : "Pad"; +const osVersion = currentDevice.systemVersion; -var GET = "GET"; -var USER_AGENT_HEADER = "User-Agent"; -var USER_AGENT = `Mozilla/5.0 (i${device}; CPU OS ${osVersion.replace(".", "_")} like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/${osVersion} Mobile/10A5355d Safari/8536.25`; -var sessionConfig = getter(NSURLSessionConfiguration, NSURLSessionConfiguration.defaultSessionConfiguration); -var queue = getter(NSOperationQueue, NSOperationQueue.mainQueue); +const GET = "GET"; +const USER_AGENT_HEADER = "User-Agent"; +const USER_AGENT = `Mozilla/5.0 (i${device}; CPU OS ${osVersion.replace(".", "_")} like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/${osVersion} Mobile/10A5355d Safari/8536.25`; +const sessionConfig = getter(NSURLSessionConfiguration, NSURLSessionConfiguration.defaultSessionConfiguration); +const queue = getter(NSOperationQueue, NSOperationQueue.mainQueue); function parseJSON(source: string): any { - var src = source.trim(); + const src = source.trim(); if (src.lastIndexOf(")") === src.length - 1) { return JSON.parse(src.substring(src.indexOf("(") + 1, src.lastIndexOf(")"))); } @@ -43,31 +43,24 @@ class NSURLSessionTaskDelegateImpl extends NSObject implements NSURLSessionTaskD completionHandler(null); } } -var sessionTaskDelegateInstance: NSURLSessionTaskDelegateImpl = NSURLSessionTaskDelegateImpl.new(); +const sessionTaskDelegateInstance: NSURLSessionTaskDelegateImpl = NSURLSessionTaskDelegateImpl.new(); -var defaultSession; +let defaultSession; function ensureDefaultSession() { if (!defaultSession) { defaultSession = NSURLSession.sessionWithConfigurationDelegateDelegateQueue(sessionConfig, null, queue); } } -var sessionNotFollowingRedirects; +let sessionNotFollowingRedirects; function ensureSessionNotFollowingRedirects() { if (!sessionNotFollowingRedirects) { sessionNotFollowingRedirects = NSURLSession.sessionWithConfigurationDelegateDelegateQueue(sessionConfig, sessionTaskDelegateInstance, queue); } } -var imageSource: typeof imageSourceModule; -function ensureImageSource() { - if (!imageSource) { - imageSource = require("image-source"); - } -} - -export function request(options: http.HttpRequestOptions): Promise { - return new Promise((resolve, reject) => { +export function request(options: HttpRequestOptions): Promise { + return new Promise((resolve, reject) => { if (!options.url) { reject(new Error("Request url was empty.")); @@ -75,10 +68,10 @@ export function request(options: http.HttpRequestOptions): Promise { addHeader(headers, key, value); @@ -125,7 +118,7 @@ export function request(options: http.HttpRequestOptions): Promise NSDataToString(data, encoding), toJSON: (encoding?: any) => parseJSON(NSDataToString(data, encoding)), toImage: () => { - ensureImageSource(); return new Promise((resolve, reject) => { (UIImage).tns_decodeImageWithDataCompletion(data, image => { if (image) { - resolve(imageSource.fromNativeSource(image)) + resolve(fromNativeSource(image)); } else { reject(new Error("Response content may not be converted to an Image")); } }); }); }, - toFile: (destinationFilePath?: string) => { - var fs: typeof fsModule = require("file-system"); - + toFile: (destinationFilePath?: string) => { if (!destinationFilePath) { destinationFilePath = getFilenameFromUrl(options.url); } if (data instanceof NSData) { + // ensure destination path exists by creating any missing parent directories + const file = File.fromPath(destinationFilePath); + data.writeToFileAtomically(destinationFilePath, true); - return fs.File.fromPath(destinationFilePath); + + return file; } else { reject(new Error(`Cannot save file with path: ${destinationFilePath}.`)); } @@ -175,7 +169,7 @@ export function request(options: http.HttpRequestOptions): Promiseheaders[key]).push(value); } else { - let values: string[] = [headers[key]]; + const values: string[] = [headers[key]]; values.push(value); headers[key] = values; }