mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
/**
|
|
* Copyright (c) Microsoft Corporation.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import childProcess from 'child_process';
|
|
import { ManualPromise } from '../../utils/manualPromise';
|
|
|
|
type DownloadFileLogger = (message: string) => void;
|
|
type DownloadFileOptions = {
|
|
progressBarName?: string,
|
|
log?: DownloadFileLogger,
|
|
userAgent?: string
|
|
};
|
|
|
|
/**
|
|
* Node.js has a bug where the process can exit with 0 code even though there was an uncaught exception.
|
|
* Thats why we execute it in a separate process and check manually if the destination file exists.
|
|
* https://github.com/microsoft/playwright/issues/17394
|
|
*/
|
|
function downloadFileOutOfProcess(url: string, destinationPath: string, options: DownloadFileOptions = {}): Promise<{ error: Error | null }> {
|
|
const cp = childProcess.fork(path.join(__dirname, 'oopDownloadMain.js'), [url, destinationPath, options.progressBarName || '', options.userAgent || '']);
|
|
const promise = new ManualPromise<{ error: Error | null }>();
|
|
cp.on('message', (message: any) => {
|
|
if (message?.method === 'log')
|
|
options.log?.(message.params.message);
|
|
});
|
|
cp.on('exit', code => {
|
|
if (code !== 0) {
|
|
promise.resolve({ error: new Error(`Download failure, code=${code}`) });
|
|
return;
|
|
}
|
|
if (!fs.existsSync(destinationPath))
|
|
promise.resolve({ error: new Error(`Download failure, ${destinationPath} does not exist`) });
|
|
else
|
|
promise.resolve({ error: null });
|
|
});
|
|
cp.on('error', error => {
|
|
promise.resolve({ error });
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
type DownloadOptions = {
|
|
progressBarName?: string,
|
|
retryCount?: number
|
|
log?: DownloadFileLogger
|
|
userAgent?: string
|
|
};
|
|
|
|
export async function download(
|
|
urls: string | string[],
|
|
destination: string,
|
|
options: DownloadOptions = {}
|
|
) {
|
|
const { progressBarName = 'file', retryCount = 3, log = () => { }, userAgent } = options;
|
|
for (let attempt = 1; attempt <= retryCount; ++attempt) {
|
|
log(
|
|
`downloading ${progressBarName} - attempt #${attempt}`
|
|
);
|
|
if (!Array.isArray(urls))
|
|
urls = [urls];
|
|
const url = urls[(attempt - 1) % urls.length];
|
|
|
|
const { error } = await downloadFileOutOfProcess(url, destination, {
|
|
progressBarName,
|
|
log,
|
|
userAgent,
|
|
});
|
|
if (!error) {
|
|
log(`SUCCESS downloading ${progressBarName}`);
|
|
break;
|
|
}
|
|
const errorMessage = error?.message || '';
|
|
log(`attempt #${attempt} - ERROR: ${errorMessage}`);
|
|
if (attempt >= retryCount)
|
|
throw error;
|
|
}
|
|
}
|