diff --git a/src/cdn/downloadUrlContents.ts b/src/cdn/downloadUrlContents.ts index efe910a..dd8bd5f 100644 --- a/src/cdn/downloadUrlContents.ts +++ b/src/cdn/downloadUrlContents.ts @@ -1,41 +1,26 @@ import * as http from 'http'; -import * as os from 'os'; -import * as nodePath from 'path'; -import checkLocalCache from './funcs/checkLocalCache'; -import createDirRecursively from './funcs/createDirRecursively'; import saveResponse from './funcs/saveResponse'; -/** Downloads the given URL and saves it to disk. Throws error if download fails. */ -export default function downloadUrlContents({ host, path, size }: {host: string, path: string, size: number}): Promise { - return new Promise((resolve, reject) => { - //Generate file name we want to save it under - //e.g. on Linux: /tmp/patcher/patch/assets_swtor_main/assets_swtor_main_-1to0/assets_swtor_main_-1to0.zip - const tempFileName = nodePath.join(os.tmpdir(), 'patcher', host, path); +/** Downloads the given URL and saves it to disk. Returns the location where file is saved under. Throws error if download fails. */ +export default function downloadUrlContents( + host: string, + path: string, + tempFileName: string, + resolve: (fileName: string) => void, + reject: (reason: Error | string) => void, +): void { + //Create HTTP request + const request = http.request({ + family: 4, + hostname: host, + path, + }, saveResponse.bind(null, tempFileName, resolve, (reason: string) => { request.abort(); reject(reason); })); - //Create parent directory recursively - const folderName = nodePath.dirname(tempFileName); - createDirRecursively(folderName).then(() => { - //Check if file already exists locally - checkLocalCache(tempFileName, size).then((cacheStatus) => { - if (cacheStatus) { - return resolve(tempFileName); - } - - //Create HTTP request - const request = http.request({ - family: 4, - hostname: host, - path, - }, saveResponse.bind(null, tempFileName, resolve, (reason: string) => { request.abort(); reject(reason); })); - - //In case of connection errors, exit early - request.on('error', (error) => { - request.abort(); - reject(error); - }); - - request.end(); - }); - }); + //In case of connection errors, exit early + request.on('error', (error) => { + request.abort(); + reject(error); }); + + request.end(); } diff --git a/src/cdn/downloadWithCurl.ts b/src/cdn/downloadWithCurl.ts new file mode 100644 index 0000000..8af60f3 --- /dev/null +++ b/src/cdn/downloadWithCurl.ts @@ -0,0 +1,19 @@ +import * as childProcess from 'child_process'; + +/** + * Downloads a file using a curl child process, to allow for speed-limiting and timeout if download is too slow. + * Takes as input the host domain name, the path and the file size + */ +export default function downloadWithCurl({ host, path, tempFileName, size, resolve, reject }: {host: string, path: string, tempFileName: string, size: number, resolve: (downloadedFile: string) => void, reject: () => void}): void { + const url = `http://${host}${(path.substr(0, 1) === '/' ? '' : '/')}${path}`; + + const parameters: string[] = [ + //... + '--output', tempFileName, + url, + ]; + + const spawnedProcess = childProcess.spawn('curl', parameters); + + resolve(tempFileName); +} diff --git a/src/cdn/downloadWrapper.ts b/src/cdn/downloadWrapper.ts new file mode 100644 index 0000000..43c23b9 --- /dev/null +++ b/src/cdn/downloadWrapper.ts @@ -0,0 +1,32 @@ +import * as os from 'os'; +import * as nodePath from 'path'; +import downloadUrlContents from './downloadUrlContents'; +import downloadWithCurl from './downloadWithCurl'; +import checkLocalCache from './funcs/checkLocalCache'; +import createDirRecursively from './funcs/createDirRecursively'; + +/** Downloads the given URL and saves it to disk. Returns the location where file is saved under. Throws error if download fails. */ +export default function downloadWrapper({ host, path, size, useCurl = false }: {host: string, path: string, size: number, useCurl: boolean}): Promise { + return new Promise((resolve, reject) => { + //Generate file name we want to save it under + //e.g. on Linux: /tmp/patcher/patch/assets_swtor_main/assets_swtor_main_-1to0/assets_swtor_main_-1to0.zip + const tempFileName = nodePath.join(os.tmpdir(), 'patcher', host, path); + + //Create parent directory recursively + const folderName = nodePath.dirname(tempFileName); + createDirRecursively(folderName).then(() => { + //Check if file already exists locally + checkLocalCache(tempFileName, size).then((cacheStatus) => { + if (cacheStatus) { + return resolve(tempFileName); + } + + if (useCurl) { + downloadWithCurl({ host, path, tempFileName, size, resolve, reject }); + } else { + downloadUrlContents(host, path, tempFileName, resolve, reject); + } + }); + }); + }); +} diff --git a/src/ssn/getPatch.ts b/src/ssn/getPatch.ts index 56312bd..e7efe69 100644 --- a/src/ssn/getPatch.ts +++ b/src/ssn/getPatch.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import * as path from 'path'; -import downloadUrlContents from '../cdn/downloadUrlContents'; +import downloadWrapper from '../cdn/downloadWrapper'; import createDirRecursively from '../cdn/funcs/createDirRecursively'; import getUrlContents from '../cdn/getUrlContents'; import { Product } from '../interfaces/ISettings'; @@ -62,7 +62,7 @@ export default async function getPatch({ product, from, to, sourceDirectory, tar //start download, making sure that .zip file downloads first const indexOfLastFile = solidpkg.files.length - 1; const zipFile = getUrlContents(createUrlObject(solidpkg.files[indexOfLastFile])); - const diskFiles = solidpkg.files.slice(0, indexOfLastFile).map((file) => downloadUrlContents(createUrlObject(file))); + const diskFiles = solidpkg.files.slice(0, indexOfLastFile).map((file) => downloadWrapper({ ...createUrlObject(file), useCurl: false })); //we can parse the file entries as soon as the .zip file is downloaded const fileEntries = readSsnFile(await zipFile); diff --git a/src/ssn/installation/launch.ts b/src/ssn/installation/launch.ts index bb0c1db..e680b4f 100644 --- a/src/ssn/installation/launch.ts +++ b/src/ssn/installation/launch.ts @@ -28,7 +28,7 @@ export default function launchProcess( parameters.push('--target', output); } - const spawnedProcess = childProcess.spawn(processPath, parameters.map((value) => value.toString(), { cwd: '.' })); + const spawnedProcess = childProcess.spawn(processPath, parameters.map((value) => value.toString())); if (typeof output === 'string') { spawnedProcess.stdout.setEncoding('utf8');