diff --git a/spec/browser/file-recovery-service-spec.js b/spec/browser/file-recovery-service-spec.js new file mode 100644 index 000000000..d4c048631 --- /dev/null +++ b/spec/browser/file-recovery-service-spec.js @@ -0,0 +1,94 @@ +'use babel' + +import FileRecoveryService from '../../src/browser/file-recovery-service' +import temp from 'temp' +import fs from 'fs-plus' +import path from 'path' +import os from 'os' +import crypto from 'crypto' +import {Emitter} from 'event-kit' + +describe("FileRecoveryService", () => { + let mockWindow, recoveryService, recoveryDirectory + + beforeEach(() => { + mockWindow = new Emitter + recoveryDirectory = path.join(os.tmpdir(), crypto.randomBytes(5).toString('hex')) + recoveryService = new FileRecoveryService(recoveryDirectory) + }) + + describe("when no crash happens during a save", () => { + it("creates a recovery file and deletes it after saving", () => { + let filePath = temp.path() + + fs.writeFileSync(filePath, "some content") + recoveryService.willSavePath({sender: mockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + + fs.writeFileSync(filePath, "changed") + recoveryService.didSavePath({sender: mockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") + }) + + it("creates many recovery files and deletes them when many windows attempt to save the same file", () => { + const anotherMockWindow = new Emitter + let filePath = temp.path() + + fs.writeFileSync(filePath, "some content") + recoveryService.willSavePath({sender: mockWindow}, filePath) + recoveryService.willSavePath({sender: anotherMockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 2) + + fs.writeFileSync(filePath, "changed") + recoveryService.didSavePath({sender: mockWindow}, filePath) + recoveryService.didSavePath({sender: anotherMockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + assert.equal(fs.readFileSync(filePath, 'utf8'), "changed") + }) + }) + + describe("when a crash happens during a save", () => { + it("restores the created recovery file and deletes it", () => { + let filePath = temp.path() + + fs.writeFileSync(filePath, "some content") + recoveryService.willSavePath({sender: mockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + + fs.writeFileSync(filePath, "changed") + mockWindow.emit("crashed") + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + assert.equal(fs.readFileSync(filePath, 'utf8'), "some content") + }) + + it("restores the created recovery files and deletes them in the order in which windows crash", () => { + const anotherMockWindow = new Emitter + let filePath = temp.path() + + fs.writeFileSync(filePath, "window 1") + recoveryService.willSavePath({sender: mockWindow}, filePath) + fs.writeFileSync(filePath, "window 2") + recoveryService.willSavePath({sender: anotherMockWindow}, filePath) + assert.equal(fs.listTreeSync(recoveryDirectory).length, 2) + + fs.writeFileSync(filePath, "changed") + + mockWindow.emit("crashed") + assert.equal(fs.readFileSync(filePath, 'utf8'), "window 1") + assert.equal(fs.listTreeSync(recoveryDirectory).length, 1) + + anotherMockWindow.emit("crashed") + assert.equal(fs.readFileSync(filePath, 'utf8'), "window 2") + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + }) + }) + + it("doesn't create a recovery file when the file that's being saved doesn't exist yet", () => { + recoveryService.willSavePath({sender: mockWindow}, "a-file-that-doesnt-exist") + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + + recoveryService.didSavePath({sender: mockWindow}, "a-file-that-doesnt-exist") + assert.equal(fs.listTreeSync(recoveryDirectory).length, 0) + }) +}) diff --git a/src/browser/file-recovery-service.js b/src/browser/file-recovery-service.js index 559c83cee..89aca30fd 100644 --- a/src/browser/file-recovery-service.js +++ b/src/browser/file-recovery-service.js @@ -24,7 +24,7 @@ export default class FileRecoveryService { } const window = event.sender - const recoveryFileName = crypto.createHash('sha1').update(path + Date.now().toString(), 'utf8').digest('hex').substring(0, 10) + const recoveryFileName = crypto.randomBytes(5).toString('hex') const recoveryPath = Path.join(this.recoveryDirectory, recoveryFileName) fs.writeFileSync(recoveryPath, fs.readFileSync(path)) @@ -42,7 +42,7 @@ export default class FileRecoveryService { didSavePath (event, path) { const window = event.sender const recoveryPathsByFilePath = this.recoveryPathsByWindowAndFilePath.get(window) - if (recoveryPathsByFilePath.has(path)) { + if (recoveryPathsByFilePath != null && recoveryPathsByFilePath.has(path)) { const recoveryPath = recoveryPathsByFilePath.get(path) fs.unlinkSync(recoveryPath) recoveryPathsByFilePath.delete(path)