diff --git a/src/Client.js b/src/Client.js index 594fce0cb..a918fb0a6 100644 --- a/src/Client.js +++ b/src/Client.js @@ -80,20 +80,20 @@ export default class Client extends EventEmitter { constructor( id: string, query: ClientQuery, - conn: ReactiveSocket, + conn: ?ReactiveSocket, logger: Logger, store: Store, + plugins: ?Plugins, ) { super(); this.connected = true; - this.plugins = []; + this.plugins = plugins ? plugins : []; this.connection = conn; this.id = id; this.query = query; this.messageIdCounter = 0; this.logger = logger; this.store = store; - this.broadcastCallbacks = new Map(); this.requestCallbacks = new Map(); @@ -104,16 +104,18 @@ export default class Client extends EventEmitter { }, }; - conn.connectionStatus().subscribe({ - onNext(payload) { - if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') { - client.connected = false; - } - }, - onSubscribe(subscription) { - subscription.request(Number.MAX_SAFE_INTEGER); - }, - }); + if (conn) { + conn.connectionStatus().subscribe({ + onNext(payload) { + if (payload.kind == 'ERROR' || payload.kind == 'CLOSED') { + client.connected = false; + } + }, + onSubscribe(subscription) { + subscription.request(Number.MAX_SAFE_INTEGER); + }, + }); + } } getDevice = (): ?BaseDevice => @@ -132,7 +134,7 @@ export default class Client extends EventEmitter { query: ClientQuery; messageIdCounter: number; plugins: Plugins; - connection: ReactiveSocket; + connection: ?ReactiveSocket; responder: PartialResponder; store: Store; @@ -337,7 +339,9 @@ export default class Client extends EventEmitter { console.debug(data, 'message:call'); this.startTimingRequestResponse({method, id, params}); - this.connection.fireAndForget({data: JSON.stringify(data)}); + if (this.connection) { + this.connection.fireAndForget({data: JSON.stringify(data)}); + } }); } @@ -369,7 +373,9 @@ export default class Client extends EventEmitter { params, }; console.debug(data, 'message:send'); - this.connection.fireAndForget({data: JSON.stringify(data)}); + if (this.connection) { + this.connection.fireAndForget({data: JSON.stringify(data)}); + } } call(api: string, method: string, params?: Object): Promise { diff --git a/src/MenuBar.js b/src/MenuBar.js index 02aaa1385..da0e61303 100644 --- a/src/MenuBar.js +++ b/src/MenuBar.js @@ -6,10 +6,12 @@ */ import type {FlipperPlugin, FlipperDevicePlugin} from './plugin.js'; -import {exportStoreToFile} from './utils/exportData.js'; +import {exportStoreToFile, importFileToStore} from './utils/exportData.js'; import type {Store} from './reducers/'; import electron from 'electron'; import {GK} from 'flipper'; +import {remote} from 'electron'; +const {dialog} = remote; export type DefaultKeyboardAction = 'clear' | 'goToBottom' | 'createPaste'; export type TopLevelMenu = 'Edit' | 'View' | 'Window' | 'Help'; @@ -315,12 +317,28 @@ function getTemplate( label: 'File', submenu: [ { - label: 'Export Data', + label: 'Export Data...', role: 'export', click: function(item: Object, focusedWindow: Object) { exportStoreToFile(store); }, }, + { + label: 'Import Data...', + role: 'import', + click: function(item: Object, focusedWindow: Object) { + dialog.showOpenDialog( + { + properties: ['openFile'], + }, + (files: Array) => { + if (files !== undefined && files.length > 0) { + importFileToStore(files[0], store); + } + }, + ); + }, + }, ], }); } diff --git a/src/chrome/MainSidebar.js b/src/chrome/MainSidebar.js index c632a676a..67a3e675f 100644 --- a/src/chrome/MainSidebar.js +++ b/src/chrome/MainSidebar.js @@ -219,7 +219,6 @@ class MainSidebar extends PureComponent { numNotifications, } = this.props; let {clients, uninitializedClients} = this.props; - clients = clients .filter( (client: Client) => diff --git a/src/devices/AndroidDevice.js b/src/devices/AndroidDevice.js index 2dd79417c..16f2b46b0 100644 --- a/src/devices/AndroidDevice.js +++ b/src/devices/AndroidDevice.js @@ -78,7 +78,7 @@ export default class AndroidDevice extends BaseDevice { }); } - spawnShell(): DeviceShell { + spawnShell(): ?DeviceShell { return child_process.spawn('adb', ['-s', this.serial, 'shell', '-t', '-t']); } } diff --git a/src/devices/ArchivedDevice.js b/src/devices/ArchivedDevice.js new file mode 100644 index 000000000..11577a96a --- /dev/null +++ b/src/devices/ArchivedDevice.js @@ -0,0 +1,23 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ +import BaseDevice from './BaseDevice.js'; +import type {DeviceType, OS, DeviceShell} from './BaseDevice.js'; + +export default class ArchivedDevice extends BaseDevice { + constructor(serial: string, deviceType: DeviceType, title: string, os: OS) { + super(serial, deviceType, title); + this.os = os; + } + + getLogs() { + return []; + } + + spawnShell(): ?DeviceShell { + return null; + } +} diff --git a/src/devices/BaseDevice.js b/src/devices/BaseDevice.js index 64174b976..3503471e0 100644 --- a/src/devices/BaseDevice.js +++ b/src/devices/BaseDevice.js @@ -86,7 +86,7 @@ export default class BaseDevice { teardown() {} supportedColumns(): Array { - throw new Error('unimplemented'); + return ['date', 'pid', 'tid', 'tag', 'message', 'type', 'time']; } addLogListener(callback: DeviceLogListener): Symbol { @@ -117,7 +117,7 @@ export default class BaseDevice { this.logListeners.delete(id); } - spawnShell(): DeviceShell { + spawnShell(): ?DeviceShell { throw new Error('unimplemented'); } } diff --git a/src/utils/__tests__/exportData.node.js b/src/utils/__tests__/exportData.electron.js similarity index 100% rename from src/utils/__tests__/exportData.node.js rename to src/utils/__tests__/exportData.electron.js diff --git a/src/utils/exportData.js b/src/utils/exportData.js index dcb4cd071..eb0af3405 100644 --- a/src/utils/exportData.js +++ b/src/utils/exportData.js @@ -13,6 +13,9 @@ import type {State as PluginStatesState} from '../reducers/pluginStates'; import type {State} from '../reducers/index'; import {FlipperDevicePlugin} from '../plugin.js'; import {default as BaseDevice} from '../devices/BaseDevice'; +import {default as ArchivedDevice} from '../devices/ArchivedDevice'; +import {default as Client} from '../Client'; +import {getInstance} from '../fb-stubs/Logger.js'; import fs from 'fs'; import os from 'os'; @@ -150,3 +153,61 @@ export const exportStoreToFile = (store: Store): Promise => { console.error('Make sure a device is connected'); return new Promise.reject(new Error('No device is selected')); }; + +export const importFileToStore = (file: string, store: Store) => { + fs.readFile(file, 'utf8', (err, data) => { + if (err) { + console.error(err); + return; + } + const json = JSON.parse(data); + const {device, clients} = json; + const archivedDevice = new ArchivedDevice( + device.serial, + device.deviceType, + device.title, + device.os, + ); + store.dispatch({ + type: 'REGISTER_DEVICE', + payload: archivedDevice, + }); + store.dispatch({ + type: 'SELECT_DEVICE', + payload: archivedDevice, + }); + + const {pluginStates} = json.store; + const keys = Object.keys(pluginStates); + clients.forEach(client => { + const clientPlugins = keys + .filter(key => { + const arr = key.split('#'); + arr.pop(); + const clientPlugin = arr.join('#'); + return client.id === clientPlugin; + }) + .map(client => client.split('#').pop()); + store.dispatch({ + type: 'NEW_CLIENT', + payload: new Client( + client.id, + client.query, + null, + getInstance(), + store, + clientPlugins, + ), + }); + }); + keys.forEach(key => { + store.dispatch({ + type: 'SET_PLUGIN_STATE', + payload: { + pluginKey: key, + state: pluginStates[key], + }, + }); + }); + }); +};