Add download file API
Summary: Changelog: Expose "downloadFile" API to Flipper plugins. Allow them to download files form the web to Flipper Server. Reviewed By: mweststrate Differential Revision: D32950685 fbshipit-source-id: 7b7f666e165ff7bf209230cdc96078272ede3616
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4cb80a452f
commit
92f0ed67f4
@@ -45,6 +45,7 @@ import {
|
||||
} from './fb-stubs/internRequests';
|
||||
import {commandNodeApiExec} from './commands/NodeApiExec';
|
||||
import {access, copyFile, mkdir, unlink} from 'fs/promises';
|
||||
import {commandDownloadFileStartFactory} from './commands/DownloadFile';
|
||||
|
||||
export const SERVICE_FLIPPER = 'flipper.oAuthToken';
|
||||
|
||||
@@ -228,6 +229,11 @@ export class FlipperServerImpl implements FlipperServer {
|
||||
'node-api-fs-unlink': unlink,
|
||||
'node-api-fs-mkdir': mkdir,
|
||||
'node-api-fs-copyFile': copyFile,
|
||||
// TODO: Unit tests
|
||||
// TODO: Do we need API to cancel an active download?
|
||||
'download-file-start': commandDownloadFileStartFactory(
|
||||
this.emit.bind(this),
|
||||
),
|
||||
'get-config': async () => this.config,
|
||||
'get-changelog': getChangelog,
|
||||
'device-list': async () => {
|
||||
|
||||
106
desktop/flipper-server-core/src/commands/DownloadFile.tsx
Normal file
106
desktop/flipper-server-core/src/commands/DownloadFile.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its 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} from 'flipper-common';
|
||||
import {pathExists} from 'fs-extra';
|
||||
import {promises, createWriteStream, ReadStream} from 'fs';
|
||||
import axios from 'axios';
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const downloadId = uuid();
|
||||
|
||||
const response = await axios.request<ReadStream>({
|
||||
method,
|
||||
url,
|
||||
responseType: 'stream',
|
||||
adapter: axiosHttpAdapter,
|
||||
timeout,
|
||||
maxRedirects,
|
||||
headers,
|
||||
});
|
||||
const totalSize = response.headers['content-length'] ?? 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',
|
||||
});
|
||||
});
|
||||
|
||||
writeStream.on('error', (e: Error) => {
|
||||
response.data.destroy();
|
||||
emit('download-file-update', {
|
||||
id: downloadId,
|
||||
downloaded,
|
||||
totalSize,
|
||||
status: 'error',
|
||||
message: e.message,
|
||||
stack: e.stack,
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
id: downloadId,
|
||||
headers: response.headers,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
totalSize,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user