From d95b15094f83e204586b0e43c631af4e57975ab8 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Mon, 13 Dec 2021 05:46:42 -0800 Subject: [PATCH] Implement fs.readFile / fs.writeFile Summary: Per title. Made an explicit distinction between binary and non binary files, since they need to be encoded differently. This keeps both the implementation and API simpler (in terms of overloading / type checking) Reviewed By: aigoncharov Differential Revision: D33016031 fbshipit-source-id: 3c99956eb016849a908a171d88a7a64a88b76268 --- desktop/flipper-common/src/server-types.tsx | 16 ++++++++++++ .../flipper-plugin/src/plugin/FlipperLib.tsx | 11 ++++++++ .../src/test-utils/test-utils.tsx | 4 +++ .../src/FlipperServerImpl.tsx | 20 ++++++++++++++- .../utils/flipperLibImplementation/index.tsx | 25 +++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) diff --git a/desktop/flipper-common/src/server-types.tsx b/desktop/flipper-common/src/server-types.tsx index d15384066..633a26752 100644 --- a/desktop/flipper-common/src/server-types.tsx +++ b/desktop/flipper-common/src/server-types.tsx @@ -163,6 +163,22 @@ export type FlipperServerCommands = { ) => Promise; 'node-api-fs-stat': (path: string) => Promise; 'node-api-fs-readlink': (path: string) => Promise; + 'node-api-fs-readfile': ( + path: string, + options?: {encoding?: BufferEncoding}, + ) => Promise; + 'node-api-fs-readfile-binary': ( + path: string, + ) => Promise; + 'node-api-fs-writefile': ( + path: string, + contents: string, + options?: {encoding?: BufferEncoding}, + ) => Promise; + 'node-api-fs-writefile-binary': ( + path: string, + base64contents: string, + ) => Promise; /** * @throws ExecError */ diff --git a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx index 57eefc4e0..e1fee4975 100644 --- a/desktop/flipper-plugin/src/plugin/FlipperLib.tsx +++ b/desktop/flipper-plugin/src/plugin/FlipperLib.tsx @@ -66,6 +66,17 @@ export type RemoteServerContext = { copyFile(src: string, dest: string, flags?: number): Promise; stat(path: string): Promise; readlink(path: string): Promise; + readFile( + path: string, + options?: {encoding?: BufferEncoding}, + ): Promise; + readFileBinary(path: string): Promise; // No Buffer, which is not a browser type + writeFile( + path: string, + contents: string, + options?: {encoding?: BufferEncoding}, + ): Promise; + writeFileBinary(path: string, contents: Uint8Array): Promise; }; downloadFile( url: string, diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index 5f33d7225..fba257db7 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -415,6 +415,10 @@ export function createMockFlipperLib(options?: StartPluginOptions): FlipperLib { constants: fsConstants, stat: jest.fn(), readlink: jest.fn(), + readFile: jest.fn(), + readFileBinary: jest.fn(), + writeFile: jest.fn(), + writeFileBinary: jest.fn(), }, downloadFile: jest.fn(), }, diff --git a/desktop/flipper-server-core/src/FlipperServerImpl.tsx b/desktop/flipper-server-core/src/FlipperServerImpl.tsx index b5cb2a7be..e6404bb50 100644 --- a/desktop/flipper-server-core/src/FlipperServerImpl.tsx +++ b/desktop/flipper-server-core/src/FlipperServerImpl.tsx @@ -48,8 +48,10 @@ import {commandDownloadFileStartFactory} from './commands/DownloadFile'; import {promises} from 'fs'; // Electron 11 runs on Node 12 which does not support fs.promises.rm import rm from 'rimraf'; +import assert from 'assert'; -const {access, copyFile, mkdir, unlink, stat, readlink} = promises; +const {access, copyFile, mkdir, unlink, stat, readlink, readFile, writeFile} = + promises; export const SERVICE_FLIPPER = 'flipper.oAuthToken'; @@ -258,6 +260,22 @@ export class FlipperServerImpl implements FlipperServer { }; }, 'node-api-fs-readlink': readlink, + 'node-api-fs-readfile': async (path, options) => { + const contents = await readFile(path, options ?? 'utf8'); + assert( + typeof contents === 'string', + `File ${path} was not read with a string encoding`, + ); + return contents; + }, + 'node-api-fs-readfile-binary': async (path) => { + const contents = await readFile(path); + return Base64.fromUint8Array(contents); + }, + 'node-api-fs-writefile': (path, contents, options) => + writeFile(path, contents, options ?? 'utf8'), + 'node-api-fs-writefile-binary': (path, base64contents) => + writeFile(path, Base64.toUint8Array(base64contents), 'binary'), // TODO: Do we need API to cancel an active download? 'download-file-start': commandDownloadFileStartFactory( this.emit.bind(this), diff --git a/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx b/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx index ad529ca0d..7463071b5 100644 --- a/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx +++ b/desktop/flipper-ui-core/src/utils/flipperLibImplementation/index.tsx @@ -29,6 +29,7 @@ import {DetailSidebarImpl} from '../../sandy-chrome/DetailSidebarImpl'; import {RenderHost} from '../../RenderHost'; import {setMenuEntries} from '../../reducers/connections'; import {downloadFileFactory} from './downloadFile'; +import {Base64} from 'js-base64'; export function initializeFlipperLibImplementation( renderHost: RenderHost, @@ -115,6 +116,30 @@ export function initializeFlipperLibImplementation( renderHost.flipperServer.exec('node-api-fs-stat', path), readlink: async (path: string) => renderHost.flipperServer.exec('node-api-fs-readlink', path), + readFile: (path, options) => + renderHost.flipperServer.exec('node-api-fs-readfile', path, options), + readFileBinary: async (path) => + Base64.toUint8Array( + await renderHost.flipperServer.exec( + 'node-api-fs-readfile-binary', + path, + ), + ), + writeFile: (path, contents, options) => + renderHost.flipperServer.exec( + 'node-api-fs-writefile', + path, + contents, + options, + ), + writeFileBinary: async (path, contents) => { + const base64contents = Base64.fromUint8Array(contents); + return await renderHost.flipperServer.exec( + 'node-api-fs-writefile-binary', + path, + base64contents, + ); + }, }, downloadFile: downloadFileFactory(renderHost), },