Files
flipper/desktop/flipper-server-core/src/commands/DownloadFile.tsx
Andrey Goncharov 39b14fc428 Track socket usage during file download
Summary: Should help to investigate further issues like https://fb.workplace.com/groups/flippersupport/permalink/1577911229356196/

Reviewed By: passy

Differential Revision: D43692927

fbshipit-source-id: 723b051f748946c89ece85fb6c11c40705196fe4
2023-03-01 05:26:00 -08:00

146 lines
4.2 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {FlipperServerCommands, FlipperServerEvents, uuid} from 'flipper-common';
import {pathExists} from 'fs-extra';
import {promises, createWriteStream, ReadStream} from 'fs';
import axios from 'axios';
import http from 'http';
import https from 'https';
const {unlink} = promises;
// Adapter which forces node.js implementation for axios instead of browser implementation
// used by default in Electron. Node.js implementation is better, because it
// supports streams which can be used for direct downloading to disk.
const axiosHttpAdapter = require('axios/lib/adapters/http'); // eslint-disable-line import/no-commonjs
export const commandDownloadFileStartFactory =
(
emit: (
event: 'download-file-update',
payload: FlipperServerEvents['download-file-update'],
) => void,
): FlipperServerCommands['download-file-start'] =>
async (
url,
dest,
{method = 'GET', timeout, maxRedirects, headers, overwrite} = {},
) => {
const destExists = await pathExists(dest);
if (destExists) {
if (!overwrite) {
throw new Error(
'FlipperServerImpl -> executing "download-file" -> path already exists and overwrite set to false',
);
}
await unlink(dest);
}
console.debug('commandDownloadFileStartFactory -> start', {
http: {
usedSockets: Object.keys(http.globalAgent.sockets).length,
freeSocket: Object.keys(http.globalAgent.freeSockets).length,
maxSockets: http.globalAgent.maxSockets,
maxTotalSockets: http.globalAgent.maxTotalSockets,
},
https: {
usedSockets: Object.keys(https.globalAgent.sockets).length,
freeSocket: Object.keys(https.globalAgent.freeSockets).length,
maxSockets: https.globalAgent.maxSockets,
maxTotalSockets: https.globalAgent.maxTotalSockets,
},
});
const downloadId = uuid();
const response = await axios.request<ReadStream>({
method,
url,
responseType: 'stream',
adapter: axiosHttpAdapter,
timeout,
maxRedirects,
headers,
});
let totalSize = parseInt(response.headers['content-length'], 10);
if (Number.isNaN(totalSize)) {
totalSize = 0;
}
const writeStream = response.data.pipe(
createWriteStream(dest, {autoClose: true}),
);
let downloaded = 0;
response.data.on('data', (data: any) => {
downloaded += Buffer.byteLength(data);
emit('download-file-update', {
id: downloadId,
downloaded,
totalSize,
status: 'downloading',
});
});
response.data.on('error', (e: Error) => {
writeStream.destroy(e);
});
writeStream.on('finish', () => {
emit('download-file-update', {
id: downloadId,
downloaded,
totalSize,
status: 'success',
});
console.debug('commandDownloadFileStartFactory -> finish', {
http: {
usedSockets: Object.keys(http.globalAgent.sockets).length,
freeSocket: Object.keys(http.globalAgent.freeSockets).length,
},
https: {
usedSockets: Object.keys(https.globalAgent.sockets).length,
freeSocket: Object.keys(https.globalAgent.freeSockets).length,
},
});
});
writeStream.on('error', (e: Error) => {
response.data.destroy();
emit('download-file-update', {
id: downloadId,
downloaded,
totalSize,
status: 'error',
message: e.message,
stack: e.stack,
});
console.debug('commandDownloadFileStartFactory -> error', {
http: {
usedSockets: Object.keys(http.globalAgent.sockets).length,
freeSocket: Object.keys(http.globalAgent.freeSockets).length,
},
https: {
usedSockets: Object.keys(https.globalAgent.sockets).length,
freeSocket: Object.keys(https.globalAgent.freeSockets).length,
},
});
});
return {
id: downloadId,
headers: response.headers,
status: response.status,
statusText: response.statusText,
totalSize,
};
};