diff --git a/spec/atom-environment-spec.js b/spec/atom-environment-spec.js index 324e9eddf..5574e9663 100644 --- a/spec/atom-environment-spec.js +++ b/spec/atom-environment-spec.js @@ -4,7 +4,6 @@ const fs = require('fs') const path = require('path') const temp = require('temp').track() const AtomEnvironment = require('../src/atom-environment') -const StorageFolder = require('../src/storage-folder') describe('AtomEnvironment', () => { afterEach(() => { diff --git a/spec/main-process/atom-application.test.js b/spec/main-process/atom-application.test.js index 90a512692..b67a98ae1 100644 --- a/spec/main-process/atom-application.test.js +++ b/spec/main-process/atom-application.test.js @@ -49,7 +49,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':3'])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':3'])) await focusWindow(window) const cursorRow = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -66,7 +66,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':2:2'])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':2:2'])) await focusWindow(window) const cursorPosition = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -83,7 +83,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath, '1\n2\n3\n4\n') const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([filePath + ':: '])) + const window = await atomApplication.launch(parseCommandLine([filePath + ':: '])) await focusWindow(window) const openedPath = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { @@ -99,11 +99,11 @@ describe('AtomApplication', function () { it('positions new windows at an offset distance from the previous window', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([makeTempDir()])) + const window1 = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window1) window1.browserWindow.setBounds({width: 400, height: 400, x: 0, y: 0}) - const window2 = atomApplication.launch(parseCommandLine([makeTempDir()])) + const window2 = await atomApplication.launch(parseCommandLine([makeTempDir()])) await focusWindow(window2) assert.notEqual(window1, window2) @@ -122,7 +122,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await emitterEventPromise(window1, 'window:locations-opened') await focusWindow(window1) @@ -135,7 +135,7 @@ describe('AtomApplication', function () { // Reuses the window when opening *files*, even if they're in a different directory // Does not change the project paths when doing so. - const reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath])) + const reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -148,7 +148,7 @@ describe('AtomApplication', function () { assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath]) // Opens new windows when opening directories - const window2 = atomApplication.launch(parseCommandLine([dirCPath])) + const window2 = await atomApplication.launch(parseCommandLine([dirCPath])) await emitterEventPromise(window2, 'window:locations-opened') assert.notEqual(window2, window1) await focusWindow(window2) @@ -163,7 +163,7 @@ describe('AtomApplication', function () { fs.writeFileSync(existingDirCFilePath, 'this is an existing file') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'new-file')])) await focusWindow(window1) let activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -175,7 +175,7 @@ describe('AtomApplication', function () { // When opening *files* with --add, reuses an existing window and adds // parent directory to the project - let reusedWindow = atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) + let reusedWindow = await atomApplication.launch(parseCommandLine([existingDirCFilePath, '--add'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) activeEditorPath = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { @@ -189,7 +189,7 @@ describe('AtomApplication', function () { // When opening *directories* with add reuses an existing window and adds // the directory to the project - reusedWindow = atomApplication.launch(parseCommandLine([dirBPath, '-a'])) + reusedWindow = await atomApplication.launch(parseCommandLine([dirBPath, '-a'])) assert.equal(reusedWindow, window1) assert.deepEqual(atomApplication.getAllWindows(), [window1]) @@ -202,7 +202,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const nonExistentFilePath = path.join(tempDirPath, 'new-file') - const window1 = atomApplication.launch(parseCommandLine([nonExistentFilePath])) + const window1 = await atomApplication.launch(parseCommandLine([nonExistentFilePath])) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(textEditor => { textEditor.insertText('Hello World!') @@ -214,7 +214,7 @@ describe('AtomApplication', function () { await window1.closedPromise // Restore unsaved state when opening the directory itself - const window2 = atomApplication.launch(parseCommandLine([tempDirPath])) + const window2 = await atomApplication.launch(parseCommandLine([tempDirPath])) await window2.loadedPromise const window2Text = await evalInWebContents(window2.browserWindow.webContents, sendBackToMainProcess => { const textEditor = atom.workspace.getActiveTextEditor() @@ -228,7 +228,7 @@ describe('AtomApplication', function () { await window2.closedPromise // Restore unsaved state when opening a path to a non-existent file in the directory - const window3 = atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) + const window3 = await atomApplication.launch(parseCommandLine([path.join(tempDirPath, 'another-non-existent-file')])) await window3.loadedPromise const window3Texts = await evalInWebContents(window3.browserWindow.webContents, (sendBackToMainProcess, nonExistentFilePath) => { sendBackToMainProcess(atom.workspace.getTextEditors().map(editor => editor.getText())) @@ -243,7 +243,7 @@ describe('AtomApplication', function () { fs.mkdirSync(dirBSubdirPath) const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) + const window1 = await atomApplication.launch(parseCommandLine([dirAPath, dirBPath])) await focusWindow(window1) assert.deepEqual(await getTreeViewRootDirectories(window1), [dirAPath, dirBPath]) @@ -252,17 +252,17 @@ describe('AtomApplication', function () { it('reuses windows with no project paths to open directories', async () => { const tempDirPath = makeTempDir() const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) - const reusedWindow = atomApplication.launch(parseCommandLine([tempDirPath])) + const reusedWindow = await atomApplication.launch(parseCommandLine([tempDirPath])) assert.equal(reusedWindow, window1) await conditionPromise(async () => (await getTreeViewRootDirectories(reusedWindow)).length > 0) }) it('opens a new window with a single untitled buffer when launched with no path, even if windows already exist', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) const window1EditorTitle = await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { sendBackToMainProcess(atom.workspace.getActiveTextEditor().getTitle()) @@ -287,7 +287,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) await focusWindow(window1) // wait a bit just to make sure we don't pass due to querying the render process before it loads @@ -302,7 +302,7 @@ describe('AtomApplication', function () { it('opens an empty text editor and loads its parent directory in the tree-view when launched with a new file path', async () => { const atomApplication = buildAtomApplication() const newFilePath = path.join(makeTempDir(), 'new-file') - const window = atomApplication.launch(parseCommandLine([newFilePath])) + const window = await atomApplication.launch(parseCommandLine([newFilePath])) await focusWindow(window) const {editorTitle, editorText} = await evalInWebContents(window.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.observeTextEditors(editor => { @@ -324,7 +324,7 @@ describe('AtomApplication', function () { atomApplication.config.set('core.disabledPackages', ['fuzzy-finder']) const remotePath = 'remote://server:3437/some/directory/path' - let window = atomApplication.launch(parseCommandLine([remotePath])) + let window = await atomApplication.launch(parseCommandLine([remotePath])) await focusWindow(window) await conditionPromise(async () => (await getProjectDirectories()).length > 0) @@ -350,9 +350,9 @@ describe('AtomApplication', function () { const tempDirPath2 = makeTempDir() const atomApplication1 = buildAtomApplication() - const app1Window1 = atomApplication1.launch(parseCommandLine([tempDirPath1])) + const app1Window1 = await atomApplication1.launch(parseCommandLine([tempDirPath1])) await emitterEventPromise(app1Window1, 'window:locations-opened') - const app1Window2 = atomApplication1.launch(parseCommandLine([tempDirPath2])) + const app1Window2 = await atomApplication1.launch(parseCommandLine([tempDirPath2])) await emitterEventPromise(app1Window2, 'window:locations-opened') await Promise.all([ @@ -361,7 +361,7 @@ describe('AtomApplication', function () { ]) const atomApplication2 = buildAtomApplication() - const [app2Window1, app2Window2] = atomApplication2.launch(parseCommandLine([])) + const [app2Window1, app2Window2] = await atomApplication2.launch(parseCommandLine([])) await Promise.all([ emitterEventPromise(app2Window1, 'window:locations-opened'), emitterEventPromise(app2Window2, 'window:locations-opened') @@ -373,9 +373,9 @@ describe('AtomApplication', function () { it('does not reopen any previously opened windows when launched with no path and `core.restorePreviousWindowsOnStart` is no', async () => { const atomApplication1 = buildAtomApplication() - const app1Window1 = atomApplication1.launch(parseCommandLine([makeTempDir()])) + const app1Window1 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window1) - const app1Window2 = atomApplication1.launch(parseCommandLine([makeTempDir()])) + const app1Window2 = await atomApplication1.launch(parseCommandLine([makeTempDir()])) await focusWindow(app1Window2) const configPath = path.join(process.env.ATOM_HOME, 'config.cson') @@ -385,7 +385,7 @@ describe('AtomApplication', function () { season.writeFileSync(configPath, config) const atomApplication2 = buildAtomApplication() - const app2Window = atomApplication2.launch(parseCommandLine([])) + const app2Window = await atomApplication2.launch(parseCommandLine([])) await focusWindow(app2Window) assert.deepEqual(app2Window.representedDirectoryPaths, []) }) @@ -405,10 +405,10 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened window is closed', async () => { - const window1 = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const window1 = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window1) - const [window2] = atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) + const [window2] = await atomApplication.launch(parseCommandLine(['--new-window', '--wait', '--pid', '102'])) await focusWindow(window2) assert.deepEqual(killedPids, []) @@ -424,7 +424,7 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened file in an existing window is closed', async () => { - const window = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) + const window = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101'])) await focusWindow(window) const filePath1 = temp.openSync('test').path @@ -432,7 +432,7 @@ describe('AtomApplication', function () { fs.writeFileSync(filePath1, 'File 1') fs.writeFileSync(filePath2, 'File 2') - const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) + const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '102', filePath1, filePath2])) assert.equal(reusedWindow, window) const activeEditorPath = await evalInWebContents(window.browserWindow.webContents, send => { @@ -467,11 +467,11 @@ describe('AtomApplication', function () { }) it('kills the specified pid after a newly-opened directory in an existing window is closed', async () => { - const window = atomApplication.launch(parseCommandLine([])) + const window = await atomApplication.launch(parseCommandLine([])) await focusWindow(window) const dirPath1 = makeTempDir() - const reusedWindow = atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) + const reusedWindow = await atomApplication.launch(parseCommandLine(['--wait', '--pid', '101', dirPath1])) assert.equal(reusedWindow, window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirPath1]) assert.deepEqual(killedPids, []) @@ -498,7 +498,7 @@ describe('AtomApplication', function () { if (process.platform === 'linux' || process.platform === 'win32') { it('quits the application', async () => { const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -508,7 +508,7 @@ describe('AtomApplication', function () { } else if (process.platform === 'darwin') { it('leaves the application open', async () => { const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) + const window = await atomApplication.launch(parseCommandLine([path.join(makeTempDir("a"), 'file-a')])) await focusWindow(window) window.close() await window.closedPromise @@ -524,7 +524,7 @@ describe('AtomApplication', function () { const dirB = makeTempDir() const atomApplication = buildAtomApplication() - const window = atomApplication.launch(parseCommandLine([dirA, dirB])) + const window = await atomApplication.launch(parseCommandLine([dirA, dirB])) await emitterEventPromise(window, 'window:locations-opened') await focusWindow(window) assert.deepEqual(await getTreeViewRootDirectories(window), [dirA, dirB]) @@ -539,7 +539,7 @@ describe('AtomApplication', function () { // Window state should be saved when the project folder is removed const atomApplication2 = buildAtomApplication() - const [window2] = atomApplication2.launch(parseCommandLine([])) + const [window2] = await atomApplication2.launch(parseCommandLine([])) await emitterEventPromise(window2, 'window:locations-opened') await focusWindow(window2) assert.deepEqual(await getTreeViewRootDirectories(window2), [dirB]) @@ -556,7 +556,7 @@ describe('AtomApplication', function () { const atomApplication = buildAtomApplication() const launchOptions = parseCommandLine([]) launchOptions.urlsToOpen = ['atom://package-with-url-main/test'] - let windows = atomApplication.launch(launchOptions) + let windows = await atomApplication.launch(launchOptions) await windows[0].loadedPromise let reached = await evalInWebContents(windows[0].browserWindow.webContents, sendBackToMainProcess => { @@ -571,9 +571,9 @@ describe('AtomApplication', function () { const dirBPath = makeTempDir('b') const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath)])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath)])) await focusWindow(window1) - const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath)])) + const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath)])) await focusWindow(window2) const fileA = path.join(dirAPath, 'file-a') @@ -597,9 +597,9 @@ describe('AtomApplication', function () { const dirAPath = makeTempDir("a") const dirBPath = makeTempDir("b") const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) + const window1 = await atomApplication.launch(parseCommandLine([path.join(dirAPath, 'file-a')])) await focusWindow(window1) - const window2 = atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) + const window2 = await atomApplication.launch(parseCommandLine([path.join(dirBPath, 'file-b')])) await focusWindow(window2) electron.app.quit() await new Promise(process.nextTick) @@ -612,8 +612,8 @@ describe('AtomApplication', function () { it('prevents quitting if user cancels when prompted to save an item', async () => { const atomApplication = buildAtomApplication() - const window1 = atomApplication.launch(parseCommandLine([])) - const window2 = atomApplication.launch(parseCommandLine([])) + const window1 = await atomApplication.launch(parseCommandLine([])) + const window2 = await atomApplication.launch(parseCommandLine([])) await Promise.all([window1.loadedPromise, window2.loadedPromise]) await evalInWebContents(window1.browserWindow.webContents, sendBackToMainProcess => { atom.workspace.getActiveTextEditor().insertText('unsaved text') diff --git a/spec/main-process/file-recovery-service.test.js b/spec/main-process/file-recovery-service.test.js index 618c30ab0..2a8f2088c 100644 --- a/spec/main-process/file-recovery-service.test.js +++ b/spec/main-process/file-recovery-service.test.js @@ -1,21 +1,21 @@ -/** @babel */ - -import {dialog} from 'electron' -import FileRecoveryService from '../../src/main-process/file-recovery-service' -import fs from 'fs-plus' -import sinon from 'sinon' -import {escapeRegExp} from 'underscore-plus' +const {dialog} = require('electron') +const FileRecoveryService = require('../../src/main-process/file-recovery-service') +const fs = require('fs-plus') +const sinon = require('sinon') +const {escapeRegExp} = require('underscore-plus') const temp = require('temp').track() describe("FileRecoveryService", () => { - let recoveryService, recoveryDirectory + let recoveryService, recoveryDirectory, spies beforeEach(() => { recoveryDirectory = temp.mkdirSync('atom-spec-file-recovery') recoveryService = new FileRecoveryService(recoveryDirectory) + spies = sinon.sandbox.create() }) afterEach(() => { + spies.restore() try { temp.cleanupSync() } catch (e) { @@ -24,38 +24,38 @@ describe("FileRecoveryService", () => { }) describe("when no crash happens during a save", () => { - it("creates a recovery file and deletes it after saving", () => { + it("creates a recovery file and deletes it after saving", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didSavePath(mockWindow, filePath) + await recoveryService.didSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") fs.removeSync(filePath) }) - it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", () => { + it("creates only one recovery file when many windows attempt to save the same file, deleting it when the last one finishes saving it", async () => { const mockWindow = {} const anotherMockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didSavePath(mockWindow, filePath) + await recoveryService.didSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") - recoveryService.didSavePath(anotherMockWindow, filePath) + await recoveryService.didSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") @@ -64,66 +64,66 @@ describe("FileRecoveryService", () => { }) describe("when a crash happens during a save", () => { - it("restores the created recovery file and deletes it", () => { + it("restores the created recovery file and deletes it", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "some content") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "changed") - recoveryService.didCrashWindow(mockWindow) + await recoveryService.didCrashWindow(mockWindow) assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) assert.equal(fs.readFileSync(filePath, 'utf8'), "some content") fs.removeSync(filePath) }) - it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", () => { + it("restores the created recovery file when many windows attempt to save the same file and one of them crashes", async () => { const mockWindow = {} const anotherMockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "A") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) fs.writeFileSync(filePath, "B") - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "C") - recoveryService.didCrashWindow(mockWindow) + await recoveryService.didCrashWindow(mockWindow) assert.equal(fs.readFileSync(filePath, 'utf8'), "A") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) fs.writeFileSync(filePath, "D") - recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.willSavePath(mockWindow, filePath) fs.writeFileSync(filePath, "E") - recoveryService.willSavePath(anotherMockWindow, filePath) + await recoveryService.willSavePath(anotherMockWindow, filePath) assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) fs.writeFileSync(filePath, "F") - recoveryService.didCrashWindow(anotherMockWindow) + await recoveryService.didCrashWindow(anotherMockWindow) assert.equal(fs.readFileSync(filePath, 'utf8'), "D") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) fs.removeSync(filePath) }) - it("emits a warning when a file can't be recovered", sinon.test(function () { + it("emits a warning when a file can't be recovered", async () => { const mockWindow = {} const filePath = temp.path() fs.writeFileSync(filePath, "content") fs.chmodSync(filePath, 0444) let logs = [] - this.stub(console, 'log', (message) => logs.push(message)) - this.stub(dialog, 'showMessageBox') + spies.stub(console, 'log', (message) => logs.push(message)) + spies.stub(dialog, 'showMessageBox') - recoveryService.willSavePath(mockWindow, filePath) - recoveryService.didCrashWindow(mockWindow) + await recoveryService.willSavePath(mockWindow, filePath) + await recoveryService.didCrashWindow(mockWindow) let recoveryFiles = fs.listTreeSync(recoveryDirectory) assert.equal(recoveryFiles.length, 1) assert.equal(logs.length, 1) @@ -131,16 +131,16 @@ describe("FileRecoveryService", () => { assert.match(logs[0], new RegExp(escapeRegExp(recoveryFiles[0]))) fs.removeSync(filePath) - })) + }) }) - it("doesn't create a recovery file when the file that's being saved doesn't exist yet", () => { + it("doesn't create a recovery file when the file that's being saved doesn't exist yet", async () => { const mockWindow = {} - recoveryService.willSavePath(mockWindow, "a-file-that-doesnt-exist") + await recoveryService.willSavePath(mockWindow, "a-file-that-doesnt-exist") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) - recoveryService.didSavePath(mockWindow, "a-file-that-doesnt-exist") + await recoveryService.didSavePath(mockWindow, "a-file-that-doesnt-exist") assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) }) }) diff --git a/src/application-delegate.js b/src/application-delegate.js index a6d701078..1cb0c740a 100644 --- a/src/application-delegate.js +++ b/src/application-delegate.js @@ -354,11 +354,11 @@ class ApplicationDelegate { } emitWillSavePath (path) { - return ipcRenderer.sendSync('will-save-path', path) + return ipcHelpers.call('will-save-path', path) } emitDidSavePath (path) { - return ipcRenderer.sendSync('did-save-path', path) + return ipcHelpers.call('did-save-path', path) } resolveProxy (requestId, url) { diff --git a/src/atom-environment.js b/src/atom-environment.js index a80cde66c..8ac507258 100644 --- a/src/atom-environment.js +++ b/src/atom-environment.js @@ -9,7 +9,6 @@ const fs = require('fs-plus') const {mapSourcePosition} = require('@atom/source-map-support') const WindowEventHandler = require('./window-event-handler') const StateStore = require('./state-store') -const StorageFolder = require('./storage-folder') const registerDefaultCommands = require('./register-default-commands') const {updateProcessEnv} = require('./update-process-env') const ConfigSchema = require('./config-schema') @@ -208,12 +207,7 @@ class AtomEnvironment { this.blobStore = params.blobStore this.configDirPath = params.configDirPath - const {devMode, safeMode, resourcePath, clearWindowState} = this.getLoadSettings() - - if (clearWindowState) { - this.getStorageFolder().clear() - this.stateStore.clear() - } + const {devMode, safeMode, resourcePath} = this.getLoadSettings() ConfigSchema.projectHome = { type: 'string', @@ -764,7 +758,11 @@ class AtomEnvironment { } // Call this method when establishing a real application window. - startEditorWindow () { + async startEditorWindow () { + if (this.getLoadSettings().clearWindowState) { + await this.stateStore.clear() + } + this.unloaded = false const updateProcessEnvPromise = this.updateProcessEnvAndTriggerHooks() @@ -1264,11 +1262,6 @@ or use Pane::saveItemAs for programmatic saving.`) } } - getStorageFolder () { - if (!this.storageFolder) this.storageFolder = new StorageFolder(this.getConfigDirPath()) - return this.storageFolder - } - getConfigDirPath () { if (!this.configDirPath) this.configDirPath = process.env.ATOM_HOME return this.configDirPath diff --git a/src/ipc-helpers.js b/src/ipc-helpers.js index 4be9f9613..b68877f99 100644 --- a/src/ipc-helpers.js +++ b/src/ipc-helpers.js @@ -1,15 +1,13 @@ -'use strict' - const Disposable = require('event-kit').Disposable let ipcRenderer = null let ipcMain = null let BrowserWindow = null +let nextResponseChannelId = 0 + exports.on = function (emitter, eventName, callback) { emitter.on(eventName, callback) - return new Disposable(function () { - emitter.removeListener(eventName, callback) - }) + return new Disposable(() => emitter.removeListener(eventName, callback)) } exports.call = function (channel, ...args) { @@ -18,34 +16,28 @@ exports.call = function (channel, ...args) { ipcRenderer.setMaxListeners(20) } - var responseChannel = getResponseChannel(channel) + const responseChannel = `ipc-helpers-response-${nextResponseChannelId++}` - return new Promise(function (resolve) { - ipcRenderer.on(responseChannel, function (event, result) { + return new Promise(resolve => { + ipcRenderer.on(responseChannel, (event, result) => { ipcRenderer.removeAllListeners(responseChannel) resolve(result) }) - ipcRenderer.send(channel, ...args) + ipcRenderer.send(channel, responseChannel, ...args) }) } exports.respondTo = function (channel, callback) { if (!ipcMain) { - var electron = require('electron') + const electron = require('electron') ipcMain = electron.ipcMain BrowserWindow = electron.BrowserWindow } - var responseChannel = getResponseChannel(channel) - - return exports.on(ipcMain, channel, function (event, ...args) { - var browserWindow = BrowserWindow.fromWebContents(event.sender) - var result = callback(browserWindow, ...args) + return exports.on(ipcMain, channel, async (event, responseChannel, ...args) => { + const browserWindow = BrowserWindow.fromWebContents(event.sender) + const result = await callback(browserWindow, ...args) event.sender.send(responseChannel, result) }) } - -function getResponseChannel (channel) { - return 'ipc-helpers-' + channel + '-response' -} diff --git a/src/main-process/atom-application.js b/src/main-process/atom-application.js index 372bd537c..fcb84aae0 100644 --- a/src/main-process/atom-application.js +++ b/src/main-process/atom-application.js @@ -169,17 +169,17 @@ class AtomApplication extends EventEmitter { this.disposable.dispose() } - launch (options) { + async launch (options) { if (options.test || options.benchmark || options.benchmarkTest) { return this.openWithOptions(options) } else if ((options.pathsToOpen && options.pathsToOpen.length > 0) || (options.urlsToOpen && options.urlsToOpen.length > 0)) { if (this.config.get('core.restorePreviousWindowsOnStart') === 'always') { - this.loadState(_.deepClone(options)) + await this.loadState(_.deepClone(options)) } return this.openWithOptions(options) } else { - return this.loadState(options) || this.openPath(options) + return (await this.loadState(options)) || this.openPath(options) } } @@ -569,15 +569,13 @@ class AtomApplication extends EventEmitter { event.returnValue = this.autoUpdateManager.getErrorMessage() })) - this.disposable.add(ipcHelpers.on(ipcMain, 'will-save-path', (event, path) => { - this.fileRecoveryService.willSavePath(this.atomWindowForEvent(event), path) - event.returnValue = true - })) + this.disposable.add(ipcHelpers.respondTo('will-save-path', (window, path) => + this.fileRecoveryService.willSavePath(window, path) + )) - this.disposable.add(ipcHelpers.on(ipcMain, 'did-save-path', (event, path) => { - this.fileRecoveryService.didSavePath(this.atomWindowForEvent(event), path) - event.returnValue = true - })) + this.disposable.add(ipcHelpers.respondTo('did-save-path', (window, path) => + this.fileRecoveryService.didSavePath(window, path) + )) this.disposable.add(ipcHelpers.on(ipcMain, 'did-change-paths', () => this.saveState(false) @@ -911,7 +909,7 @@ class AtomApplication extends EventEmitter { } } - saveState (allowEmpty = false) { + async saveState (allowEmpty = false) { if (this.quitting) return const states = [] @@ -921,13 +919,13 @@ class AtomApplication extends EventEmitter { states.reverse() if (states.length > 0 || allowEmpty) { - this.storageFolder.storeSync('application.json', states) + await this.storageFolder.store('application.json', states) this.emit('application:did-save-state') } } - loadState (options) { - const states = this.storageFolder.load('application.json') + async loadState (options) { + const states = await this.storageFolder.load('application.json') if ( ['yes', 'always'].includes(this.config.get('core.restorePreviousWindowsOnStart')) && states && states.length > 0 diff --git a/src/main-process/atom-window.js b/src/main-process/atom-window.js index 0268cc1cf..9ffd1e6c0 100644 --- a/src/main-process/atom-window.js +++ b/src/main-process/atom-window.js @@ -186,14 +186,14 @@ class AtomWindow extends EventEmitter { if (chosen === 0) this.browserWindow.destroy() }) - this.browserWindow.webContents.on('crashed', () => { + this.browserWindow.webContents.on('crashed', async () => { if (this.headless) { console.log('Renderer process crashed, exiting') this.atomApplication.exit(100) return } - this.fileRecoveryService.didCrashWindow(this) + await this.fileRecoveryService.didCrashWindow(this) const chosen = dialog.showMessageBox(this.browserWindow, { type: 'warning', buttons: ['Close Window', 'Reload', 'Keep It Open'], diff --git a/src/main-process/file-recovery-service.js b/src/main-process/file-recovery-service.js index f55e3f956..58ca84943 100644 --- a/src/main-process/file-recovery-service.js +++ b/src/main-process/file-recovery-service.js @@ -1,11 +1,10 @@ -'use babel' +const {dialog} = require('electron') +const crypto = require('crypto') +const Path = require('path') +const fs = require('fs-plus') -import {dialog} from 'electron' -import crypto from 'crypto' -import Path from 'path' -import fs from 'fs-plus' - -export default class FileRecoveryService { +module.exports = +class FileRecoveryService { constructor (recoveryDirectory) { this.recoveryDirectory = recoveryDirectory this.recoveryFilesByFilePath = new Map() @@ -13,15 +12,16 @@ export default class FileRecoveryService { this.windowsByRecoveryFile = new Map() } - willSavePath (window, path) { - if (!fs.existsSync(path)) return + async willSavePath (window, path) { + const stats = await tryStatFile(path) + if (!stats) return const recoveryPath = Path.join(this.recoveryDirectory, RecoveryFile.fileNameForPath(path)) const recoveryFile = - this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, recoveryPath) + this.recoveryFilesByFilePath.get(path) || new RecoveryFile(path, stats.mode, recoveryPath) try { - recoveryFile.retain() + await recoveryFile.retain() } catch (err) { console.log(`Couldn't retain ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) return @@ -39,11 +39,11 @@ export default class FileRecoveryService { this.recoveryFilesByFilePath.set(path, recoveryFile) } - didSavePath (window, path) { + async didSavePath (window, path) { const recoveryFile = this.recoveryFilesByFilePath.get(path) if (recoveryFile != null) { try { - recoveryFile.release() + await recoveryFile.release() } catch (err) { console.log(`Couldn't release ${recoveryFile.recoveryPath}. Code: ${err.code}. Message: ${err.message}`) } @@ -53,27 +53,31 @@ export default class FileRecoveryService { } } - didCrashWindow (window) { + async didCrashWindow (window) { if (!this.recoveryFilesByWindow.has(window)) return + const promises = [] for (const recoveryFile of this.recoveryFilesByWindow.get(window)) { - try { - recoveryFile.recoverSync() - } catch (error) { - const message = 'A file that Atom was saving could be corrupted' - const detail = - `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + - `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` - console.log(detail) - dialog.showMessageBox(window.browserWindow, {type: 'info', buttons: ['OK'], message, detail}) - } finally { - for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { - this.recoveryFilesByWindow.get(window).delete(recoveryFile) - } - this.windowsByRecoveryFile.delete(recoveryFile) - this.recoveryFilesByFilePath.delete(recoveryFile.originalPath) - } + promises.push(recoveryFile.recover() + .catch(error => { + const message = 'A file that Atom was saving could be corrupted' + const detail = + `Error ${error.code}. There was a crash while saving "${recoveryFile.originalPath}", so this file might be blank or corrupted.\n` + + `Atom couldn't recover it automatically, but a recovery file has been saved at: "${recoveryFile.recoveryPath}".` + console.log(detail) + dialog.showMessageBox(window, {type: 'info', buttons: ['OK'], message, detail}) + }) + .then(() => { + for (let window of this.windowsByRecoveryFile.get(recoveryFile)) { + this.recoveryFilesByWindow.get(window).delete(recoveryFile) + } + this.windowsByRecoveryFile.delete(recoveryFile) + this.recoveryFilesByFilePath.delete(recoveryFile.originalPath) + }) + ) } + + await Promise.all(promises) } didCloseWindow (window) { @@ -94,36 +98,64 @@ class RecoveryFile { return `${basename}-${randomSuffix}${extension}` } - constructor (originalPath, recoveryPath) { + constructor (originalPath, fileMode, recoveryPath) { this.originalPath = originalPath + this.fileMode = fileMode this.recoveryPath = recoveryPath this.refCount = 0 } - storeSync () { - fs.copyFileSync(this.originalPath, this.recoveryPath) + async store () { + await copyFile(this.originalPath, this.recoveryPath, this.fileMode) } - recoverSync () { - fs.copyFileSync(this.recoveryPath, this.originalPath) - this.removeSync() + async recover () { + await copyFile(this.recoveryPath, this.originalPath, this.fileMode) + await this.remove() } - removeSync () { - fs.unlinkSync(this.recoveryPath) + async remove () { + return new Promise((resolve, reject) => + fs.unlink(this.recoveryPath, error => + error && error.code !== 'ENOENT' ? reject(error) : resolve() + ) + ) } - retain () { - if (this.isReleased()) this.storeSync() + async retain () { + if (this.isReleased()) await this.store() this.refCount++ } - release () { + async release () { this.refCount-- - if (this.isReleased()) this.removeSync() + if (this.isReleased()) await this.remove() } isReleased () { return this.refCount === 0 } } + +async function tryStatFile (path) { + return new Promise((resolve, reject) => + fs.stat(path, (error, result) => + resolve(error == null && result) + ) + ) +} + +async function copyFile (source, destination, mode) { + return new Promise((resolve, reject) => { + const readStream = fs.createReadStream(source) + readStream + .on('error', reject) + .once('open', () => { + const writeStream = fs.createWriteStream(destination, {mode}) + writeStream + .on('error', reject) + .on('open', () => readStream.pipe(writeStream)) + .once('close', () => resolve()) + }) + }) +} diff --git a/src/project.js b/src/project.js index 18e71c915..9cd2d1245 100644 --- a/src/project.js +++ b/src/project.js @@ -695,7 +695,7 @@ class Project extends Model { } subscribeToBuffer (buffer) { - buffer.onWillSave(({path}) => this.applicationDelegate.emitWillSavePath(path)) + buffer.onWillSave(async ({path}) => this.applicationDelegate.emitWillSavePath(path)) buffer.onDidSave(({path}) => this.applicationDelegate.emitDidSavePath(path)) buffer.onDidDestroy(() => this.removeBuffer(buffer)) buffer.onDidChangePath(() => { diff --git a/src/storage-folder.coffee b/src/storage-folder.coffee deleted file mode 100644 index 280eb8b5c..000000000 --- a/src/storage-folder.coffee +++ /dev/null @@ -1,39 +0,0 @@ -path = require "path" -fs = require "fs-plus" - -module.exports = -class StorageFolder - constructor: (containingPath) -> - @path = path.join(containingPath, "storage") if containingPath? - - clear: -> - return unless @path? - - try - fs.removeSync(@path) - catch error - console.warn "Error deleting #{@path}", error.stack, error - - storeSync: (name, object) -> - return unless @path? - - fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8') - - load: (name) -> - return unless @path? - - statePath = @pathForKey(name) - try - stateString = fs.readFileSync(statePath, 'utf8') - catch error - unless error.code is 'ENOENT' - console.warn "Error reading state file: #{statePath}", error.stack, error - return undefined - - try - JSON.parse(stateString) - catch error - console.warn "Error parsing state file: #{statePath}", error.stack, error - - pathForKey: (name) -> path.join(@getPath(), name) - getPath: -> @path diff --git a/src/storage-folder.js b/src/storage-folder.js new file mode 100644 index 000000000..4931dab11 --- /dev/null +++ b/src/storage-folder.js @@ -0,0 +1,49 @@ +const path = require('path') +const fs = require('fs-plus') + +module.exports = +class StorageFolder { + constructor (containingPath) { + if (containingPath) { + this.path = path.join(containingPath, 'storage') + } + } + + store (name, object) { + return new Promise((resolve, reject) => { + if (!this.path) return resolve() + fs.writeFile(this.pathForKey(name), JSON.stringify(object), 'utf8', error => + error ? reject(error) : resolve() + ) + }) + } + + load (name) { + return new Promise(resolve => { + if (!this.path) return resolve(null) + const statePath = this.pathForKey(name) + fs.readFile(statePath, 'utf8', (error, stateString) => { + if (error && error.code !== 'ENOENT') { + console.warn(`Error reading state file: ${statePath}`, error.stack, error) + } + + if (!stateString) return resolve(null) + + try { + resolve(JSON.parse(stateString)) + } catch (error) { + console.warn(`Error parsing state file: ${statePath}`, error.stack, error) + resolve(null) + } + }) + }) + } + + pathForKey (name) { + return path.join(this.getPath(), name) + } + + getPath () { + return this.path + } +}