Screen capture unique file names and location

Summary: Screen captures now have unique names, so they don't get overwritten. By default they are saved to the desktop, but the path can be overwritten by setting `screenCapturePath` in `~/.sonar/config.js`

Reviewed By: jknoxville

Differential Revision: D9120822

fbshipit-source-id: ab6880eac475da3839f08c6e644c16bdc8693647
This commit is contained in:
Daniel Büchele
2018-08-01 11:11:48 -07:00
committed by Facebook Github Bot
parent d2708d4982
commit 06e70a1555
2 changed files with 46 additions and 33 deletions

View File

@@ -12,17 +12,18 @@ import IOSDevice from '../devices/IOSDevice';
import os from 'os'; import os from 'os';
import fs from 'fs'; import fs from 'fs';
import adb from 'adbkit-fb'; import adb from 'adbkit-fb';
import path from 'path';
import {exec} from 'child_process'; import {exec} from 'child_process';
import {remote} from 'electron';
import path from 'path';
const SCREENSHOT_FILE_NAME = 'screen.png'; let CAPTURE_LOCATION = remote.app.getPath('desktop');
const VIDEO_FILE_NAME = 'video.mp4'; try {
const SCREENSHOT_PATH = path.join( CAPTURE_LOCATION =
JSON.parse(window.process.env.CONFIG).screenCapturePath.replace(
/^~/,
os.homedir(), os.homedir(),
'/.sonar/', ) || CAPTURE_LOCATION;
SCREENSHOT_FILE_NAME, } catch (e) {}
);
const VIDEO_PATH = path.join(os.homedir(), '.sonar', VIDEO_FILE_NAME);
import type BaseDevice from '../devices/BaseDevice'; import type BaseDevice from '../devices/BaseDevice';
@@ -39,9 +40,9 @@ type State = {|
capturingScreenshot: boolean, capturingScreenshot: boolean,
|}; |};
function openFile(path: string): Promise<*> { function openFile(path: string): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec(`${getOpenCommand()} ${path}`, (error, stdout, stderr) => { exec(`${getOpenCommand()} "${path}"`, (error, stdout, stderr) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
@@ -63,18 +64,24 @@ function getOpenCommand(): string {
} }
} }
function getFileName(extension: 'png' | 'mp4'): string {
return `Screen Capture ${new Date().toISOString()}.${extension}`;
}
function writePngStreamToFile(stream: PullTransfer): Promise<string> { function writePngStreamToFile(stream: PullTransfer): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const pngPath = path.join(CAPTURE_LOCATION, getFileName('png'));
stream.on('end', () => { stream.on('end', () => {
resolve(SCREENSHOT_PATH); resolve(pngPath);
}); });
stream.on('error', reject); stream.on('error', reject);
stream.pipe(fs.createWriteStream(SCREENSHOT_PATH)); stream.pipe(fs.createWriteStream(pngPath));
}); });
} }
class ScreenCaptureButtons extends Component<Props, State> { class ScreenCaptureButtons extends Component<Props, State> {
iOSRecorder: ?any; iOSRecorder: ?any;
videoPath: ?string;
state = { state = {
pullingData: false, pullingData: false,
@@ -117,7 +124,7 @@ class ScreenCaptureButtons extends Component<Props, State> {
} }
}; };
captureScreenshot = () => { captureScreenshot = (): ?Promise<string> => {
const {selectedDevice} = this.props; const {selectedDevice} = this.props;
if (selectedDevice instanceof AndroidDevice) { if (selectedDevice instanceof AndroidDevice) {
@@ -127,29 +134,33 @@ class ScreenCaptureButtons extends Component<Props, State> {
.then(openFile) .then(openFile)
.catch(console.error); .catch(console.error);
} else if (selectedDevice instanceof IOSDevice) { } else if (selectedDevice instanceof IOSDevice) {
const screenshotPath = path.join(CAPTURE_LOCATION, getFileName('png'));
return new Promise((resolve, reject) => {
exec( exec(
`xcrun simctl io booted screenshot ${SCREENSHOT_PATH}`, `xcrun simctl io booted screenshot "${screenshotPath}"`,
(err, data) => { async (err, data) => {
if (err) { if (err) {
console.error(err); reject(err);
} else { } else {
openFile(SCREENSHOT_PATH); resolve(await openFile(screenshotPath));
} }
}, },
); );
});
} }
}; };
startRecording = () => { startRecording = () => {
const {selectedDevice} = this.props; const {selectedDevice} = this.props;
const videoPath = path.join(CAPTURE_LOCATION, getFileName('mp4'));
this.videoPath = videoPath;
if (selectedDevice instanceof AndroidDevice) { if (selectedDevice instanceof AndroidDevice) {
this.setState({ this.setState({
recording: true, recording: true,
}); });
this.executeShell( this.executeShell(
selectedDevice, selectedDevice,
`screenrecord --bugreport /sdcard/${VIDEO_FILE_NAME}`, `screenrecord --bugreport /sdcard/video.mp4`,
) )
.then(output => { .then(output => {
if (output) { if (output) {
@@ -166,14 +177,14 @@ class ScreenCaptureButtons extends Component<Props, State> {
(): Promise<string> => { (): Promise<string> => {
return this.pullFromDevice( return this.pullFromDevice(
selectedDevice, selectedDevice,
`/sdcard/${VIDEO_FILE_NAME}`, `/sdcard/video.mp4`,
VIDEO_PATH, videoPath,
); );
}, },
) )
.then(openFile) .then(openFile)
.then(() => { .then(() => {
this.executeShell(selectedDevice, `rm /sdcard/${VIDEO_FILE_NAME}`); this.executeShell(selectedDevice, `rm /sdcard/video.mp4`);
}) })
.then(() => { .then(() => {
this.setState({ this.setState({
@@ -192,7 +203,7 @@ class ScreenCaptureButtons extends Component<Props, State> {
recording: true, recording: true,
}); });
this.iOSRecorder = exec( this.iOSRecorder = exec(
`xcrun simctl io booted recordVideo ${VIDEO_PATH}`, `xcrun simctl io booted recordVideo "${videoPath}"`,
); );
} }
}; };
@@ -214,16 +225,18 @@ class ScreenCaptureButtons extends Component<Props, State> {
}; };
stopRecording = () => { stopRecording = () => {
const {videoPath} = this;
const {selectedDevice} = this.props; const {selectedDevice} = this.props;
this.videoPath = null;
if (selectedDevice instanceof AndroidDevice) { if (selectedDevice instanceof AndroidDevice) {
this.executeShell(selectedDevice, `pgrep 'screenrecord' -L 2`); this.executeShell(selectedDevice, `pgrep 'screenrecord' -L 2`);
} else if (this.iOSRecorder) { } else if (this.iOSRecorder && videoPath) {
this.iOSRecorder.kill(); this.iOSRecorder.kill();
this.setState({ this.setState({
recording: false, recording: false,
}); });
openFile(VIDEO_PATH); openFile(videoPath);
} }
}; };

View File

@@ -163,7 +163,7 @@ type Props = {
/** /**
* onClick handler. * onClick handler.
*/ */
onClick?: (event: SyntheticMouseEvent<>) => void, onClick?: (event: SyntheticMouseEvent<>) => any,
/** /**
* Whether this button is disabled. * Whether this button is disabled.
*/ */