From 74d7359cbe075fb5b55500c283d72f0b6c4aaefd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Mon, 24 Jun 2019 03:32:17 -0700 Subject: [PATCH] archive disconnected Android devices Summary: Adding a `archive` method to Android devices, that returns a new ArchivedDevice with the same properties as the Android device. This method is called when an android device disconnects and the new ArchivedDevice is added to the devices list. When the device reconnects again, the archived device is removed. Currently only logs are persisted. In following diffs we can: - add support for iOS - move the persisted pluginStates to the archived device as well Reviewed By: passy Differential Revision: D15942904 fbshipit-source-id: 07c5415994594abd630d0c4b458b76d1aac6ef02 --- src/chrome/DevicesButton.js | 13 +++++++------ src/devices/AndroidDevice.js | 11 +++++++++++ src/devices/ArchivedDevice.js | 2 ++ src/devices/BaseDevice.js | 6 ++++++ src/dispatcher/androidDevice.js | 34 +++++++++++++++++++++++++++++++++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/chrome/DevicesButton.js b/src/chrome/DevicesButton.js index ac9165772..0a4e0b62d 100644 --- a/src/chrome/DevicesButton.js +++ b/src/chrome/DevicesButton.js @@ -6,7 +6,6 @@ */ import {Component, Button, styled} from 'flipper'; -import ArchivedDevice from '../devices/ArchivedDevice.js'; import {connect} from 'react-redux'; import {spawn} from 'child_process'; import {dirname} from 'path'; @@ -65,7 +64,7 @@ class DevicesButton extends Component { let buttonLabel = 'No device selected'; let icon = 'minus-circle'; - if (selectedDevice instanceof ArchivedDevice) { + if (selectedDevice?.isArchived) { buttonLabel = `${selectedDevice?.title || 'Unknown device'} (offline)`; icon = 'box'; } else if (selectedDevice?.deviceType === 'physical') { @@ -117,11 +116,11 @@ class DevicesButton extends Component { // Archived const importedFiles = [ { - label: 'Imported Devices', + label: 'Disconnected Devices', enabled: false, }, ...devices - .filter(device => device instanceof ArchivedDevice) + .filter(device => device.isArchived) .map((device: BaseDevice) => ({ click: () => selectDevice(device), checked: device === selectedDevice, @@ -137,8 +136,10 @@ class DevicesButton extends Component { const emulators = Array.from(androidEmulators) .filter( (name: string) => - devices.findIndex((device: BaseDevice) => device.title === name) === - -1, + devices.findIndex( + (device: BaseDevice) => + device.title === name && !device.isArchived, + ) === -1, ) .map((name: string) => ({ label: name, diff --git a/src/devices/AndroidDevice.js b/src/devices/AndroidDevice.js index 24b5ac850..0c48b39c5 100644 --- a/src/devices/AndroidDevice.js +++ b/src/devices/AndroidDevice.js @@ -11,6 +11,7 @@ import {Priority} from 'adbkit-logcat-fb'; import child_process from 'child_process'; import child_process_promise from 'child-process-es6-promise'; import BaseDevice from './BaseDevice.js'; +import ArchivedDevice from './ArchivedDevice.js'; type ADBClient = any; @@ -85,4 +86,14 @@ export default class AndroidDevice extends BaseDevice { clearLogs(): Promise { return child_process_promise.spawn('adb', ['logcat', '-c']); } + + archive(): ArchivedDevice { + return new ArchivedDevice( + this.serial, + this.deviceType, + this.title, + this.os, + [...this.logEntries], + ); + } } diff --git a/src/devices/ArchivedDevice.js b/src/devices/ArchivedDevice.js index 1ae4bd47e..faf324583 100644 --- a/src/devices/ArchivedDevice.js +++ b/src/devices/ArchivedDevice.js @@ -33,6 +33,8 @@ export default class ArchivedDevice extends BaseDevice { logs: Array; + isArchived = true; + getLogs() { return this.logs; } diff --git a/src/devices/BaseDevice.js b/src/devices/BaseDevice.js index 93e2d80c7..c44eb5e9a 100644 --- a/src/devices/BaseDevice.js +++ b/src/devices/BaseDevice.js @@ -6,6 +6,7 @@ */ import type stream from 'stream'; +import type ArchivedDevice from './ArchivedDevice'; export type LogLevel = | 'unknown' @@ -74,6 +75,7 @@ export default class BaseDevice { logListeners: Map = new Map(); logEntries: Array = []; + isArchived: boolean = false; supportsOS(os: OS) { return os.toLowerCase() === this.os.toLowerCase(); @@ -135,4 +137,8 @@ export default class BaseDevice { spawnShell(): ?DeviceShell { throw new Error('unimplemented'); } + + archive(): ?ArchivedDevice { + return null; + } } diff --git a/src/dispatcher/androidDevice.js b/src/dispatcher/androidDevice.js index 434e0b6e9..64b3c65d2 100644 --- a/src/dispatcher/androidDevice.js +++ b/src/dispatcher/androidDevice.js @@ -158,6 +158,21 @@ export default (store: Store, logger: Logger) => { name: androidDevice.title, serial: androidDevice.serial, }); + + // remove offline devices with same serial as the connected. + const reconnectedDevices = store + .getState() + .connections.devices.filter( + (device: BaseDevice) => + device.serial === androidDevice.serial && device.isArchived, + ) + .map(device => device.serial); + + store.dispatch({ + type: 'UNREGISTER_DEVICES', + payload: new Set(reconnectedDevices), + }); + store.dispatch({ type: 'REGISTER_DEVICE', payload: androidDevice, @@ -178,10 +193,29 @@ export default (store: Store, logger: Logger) => { serial: id, }), ); + + const archivedDevices = deviceIds + .map(id => { + const device = store + .getState() + .connections.devices.find(device => device.serial === id); + if (device && !device.isArchived) { + return device.archive(); + } + }) + .filter(Boolean); + store.dispatch({ type: 'UNREGISTER_DEVICES', payload: new Set(deviceIds), }); + + archivedDevices.forEach((payload: BaseDevice) => + store.dispatch({ + type: 'REGISTER_DEVICE', + payload, + }), + ); } watchAndroidDevices();