-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preserve files on exit (aka hot exit) #12400
Changes from 34 commits
c410c94
ce42316
90e9796
33e6a47
39faa28
9c9c0b2
9c0d19a
a8b4f27
9e48499
038f756
2ded2a7
24adcf7
08fad0b
54108a5
d118598
c860d62
a638f02
be58446
836cbac
61d2696
163dfff
2cac437
f7ea530
660f6bd
86e1c20
4928801
701fe8c
e4f0dec
52c8af5
550da5e
e055ced
5f230ed
4fcfdc6
a822c88
1a29d14
3387b6c
c280e0c
10aa709
69a6a18
9d4ec99
a002932
dcd624c
2b0a65a
9aa7b9c
1a46093
084764d
da56a72
e53f008
9e5a282
f9cc78f
ecc83b8
30bb0b7
eb2a8e1
a040237
36f1d70
e9be330
0c48efc
4e34bfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
'use strict'; | ||
|
||
import * as fs from 'original-fs'; | ||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; | ||
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; | ||
|
||
export const IBackupService = createDecorator<IBackupService>('backupService'); | ||
|
||
export interface IBackupService { | ||
getBackupWorkspaces(): string[]; | ||
clearBackupWorkspaces(): void; | ||
pushBackupWorkspaces(workspaces: string[]): void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite clear here what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "workspaces" here is an absolute path to a "folderWorkspaces" entry within workspaces.json. For example in the following: {
"folderWorkspaces": {
"/home/daimms/dev/Microsoft/vscode/src": [
"/home/daimms/dev/Microsoft/vscode/src/bootstrap.js"
]
}
}
|
||
getBackupFiles(workspace: string): string[]; | ||
} | ||
|
||
interface IBackupFormat { | ||
folderWorkspaces?: { | ||
[workspacePath: string]: string[] | ||
}; | ||
} | ||
|
||
export class BackupService implements IBackupService { | ||
|
||
private fileContent: IBackupFormat; | ||
|
||
constructor( | ||
@IEnvironmentService private environmentService: IEnvironmentService | ||
) { | ||
} | ||
|
||
public getBackupWorkspaces(): string[] { | ||
if (!this.fileContent) { | ||
this.load(); | ||
} | ||
return Object.keys(this.fileContent.folderWorkspaces || Object.create(null)); | ||
} | ||
|
||
public clearBackupWorkspaces(): void { | ||
this.fileContent = { | ||
folderWorkspaces: Object.create(null) | ||
}; | ||
this.save(); | ||
} | ||
|
||
public pushBackupWorkspaces(workspaces: string[]): void { | ||
this.load(); | ||
workspaces.forEach(workspace => { | ||
if (!this.fileContent.folderWorkspaces[workspace]) { | ||
this.fileContent.folderWorkspaces[workspace] = []; | ||
} | ||
}); | ||
this.save(); | ||
} | ||
|
||
public getBackupFiles(workspace: string): string[] { | ||
this.load(); | ||
return this.fileContent.folderWorkspaces[workspace]; | ||
} | ||
|
||
private load(): void { | ||
try { | ||
this.fileContent = JSON.parse(fs.readFileSync(this.environmentService.backupWorkspacesPath).toString()); // invalid JSON or permission issue can happen here | ||
} catch (error) { | ||
this.fileContent = Object.create(null); | ||
} | ||
if (Array.isArray(this.fileContent) || typeof this.fileContent !== 'object') { | ||
this.fileContent = Object.create(null); | ||
} | ||
if (!this.fileContent.folderWorkspaces) { | ||
this.fileContent.folderWorkspaces = Object.create(null); | ||
} | ||
} | ||
|
||
private save(): void { | ||
try { | ||
fs.writeFileSync(this.environmentService.backupWorkspacesPath, JSON.stringify(this.fileContent)); | ||
} catch (error) { | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ import * as types from 'vs/base/common/types'; | |
import * as arrays from 'vs/base/common/arrays'; | ||
import { assign, mixin } from 'vs/base/common/objects'; | ||
import { EventEmitter } from 'events'; | ||
import { IBackupService } from 'vs/code/electron-main/backup'; | ||
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; | ||
import { IStorageService } from 'vs/code/electron-main/storage'; | ||
import { IPath, VSCodeWindow, ReadyState, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, IWindowSettings } from 'vs/code/electron-main/window'; | ||
|
@@ -49,6 +50,7 @@ export interface IOpenConfiguration { | |
forceEmpty?: boolean; | ||
windowToUse?: VSCodeWindow; | ||
diffMode?: boolean; | ||
restoreBackups?: boolean; | ||
} | ||
|
||
interface IWindowState { | ||
|
@@ -166,7 +168,8 @@ export class WindowsManager implements IWindowsService { | |
@IEnvironmentService private environmentService: IEnvironmentService, | ||
@ILifecycleService private lifecycleService: ILifecycleService, | ||
@IUpdateService private updateService: IUpdateService, | ||
@IConfigurationService private configurationService: IConfigurationService | ||
@IConfigurationService private configurationService: IConfigurationService, | ||
@IBackupService private backupService: IBackupService | ||
) { } | ||
|
||
onOpen(clb: (path: IPath) => void): () => void { | ||
|
@@ -602,6 +605,18 @@ export class WindowsManager implements IWindowsService { | |
iPathsToOpen = this.cliToPaths(openConfig.cli, ignoreFileNotFound); | ||
} | ||
|
||
// Add any existing backup workspaces | ||
if (openConfig.restoreBackups) { | ||
// TODO: Ensure the workspaces being added actually have backups | ||
this.backupService.getBackupWorkspaces().forEach(ws => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if a window is already opened with a workspace that has backup files, I think you need to avoid opening those again (since we prevent opening the same workspace twice we would just focus that window). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Provided I understand correctly, does opening the same folder twice restore backups? This should never happen as |
||
iPathsToOpen.push(this.toIPath(ws)); | ||
}); | ||
// Get rid of duplicates | ||
iPathsToOpen = arrays.distinct(iPathsToOpen, path => { | ||
return path.workspacePath; | ||
}); | ||
} | ||
|
||
let filesToOpen: IPath[] = []; | ||
let filesToDiff: IPath[] = []; | ||
let foldersToOpen = iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath); | ||
|
@@ -730,6 +745,12 @@ export class WindowsManager implements IWindowsService { | |
// Emit events | ||
iPathsToOpen.forEach(iPath => this.eventEmitter.emit(EventTypes.OPEN, iPath)); | ||
|
||
// Add to backups | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we adding these workspaces again to backup service? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intent here is for workspaces.json to contain a key for each workspace currently opened in order to support the crash recovery use case, so it's being added on the main process side when the window is launched. This is also used to determine whether a single window is opened (there is probably another way to do this) in order to decide whether the backups can be discarded safely, but that's not the main reason for it. |
||
console.log('iPathsToOpen', iPathsToOpen); | ||
this.backupService.pushBackupWorkspaces(iPathsToOpen.map((path) => { | ||
return path.workspacePath; | ||
})); | ||
|
||
return arrays.distinct(usedWindows); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
|
||
'use strict'; | ||
|
||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; | ||
import Uri from 'vs/base/common/uri'; | ||
|
||
export const IBackupService = createDecorator<IBackupService>('backupService'); | ||
|
||
export interface IBackupService { | ||
_serviceBrand: any; | ||
|
||
getBackupWorkspaces(): string[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now I am confused why this guy reaches into the territory of vs/electron-main/backup There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed, I'll merge these services as there is no reason for them not to share code. |
||
clearBackupWorkspaces(): void; | ||
removeWorkspace(workspace: string): void; | ||
|
||
registerBackupFile(resource: Uri): void; | ||
deregisterBackupFile(resource: Uri): void; | ||
getBackupFiles(workspace: string): string[]; | ||
getBackupUntitledFiles(workspace: string): string[]; | ||
getBackupResource(resource: Uri): Uri; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,27 @@ export interface IFileService { | |
*/ | ||
del(resource: URI, useTrash?: boolean): TPromise<void>; | ||
|
||
/** | ||
* Backs up the provided file to a temporary directory to be used by the hot | ||
* exit feature and crash recovery. | ||
*/ | ||
backupFile(resource: URI, content: string): TPromise<IFileStat>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think all of this should move into textfile.ts (ITextFileService) because in here we talk about all files but for hot exit we only care about text files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For hot exit we care about untitled files as well, I believe you told me to put it in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meanwhile moved ITextFileService out of file contrib land into the workbench so there was a chance of layer breaking. But now that the service is in workbench core it is on the same level as untitled files. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, so I should move the backup logic to |
||
|
||
/** | ||
* Discard the backup for the resource specified. | ||
*/ | ||
discardBackup(resource: URI): TPromise<void>; | ||
|
||
/** | ||
* Discards all backups associated with this session. | ||
*/ | ||
discardBackups(): TPromise<void>; | ||
|
||
/** | ||
* Whether hot exit is enabled. | ||
*/ | ||
isHotExitEnabled(): boolean; | ||
|
||
/** | ||
* Imports the file to the parent identified by the resource. | ||
*/ | ||
|
@@ -475,6 +496,7 @@ export interface IFilesConfiguration { | |
autoSave: string; | ||
autoSaveDelay: number; | ||
eol: string; | ||
hotExit: boolean; | ||
}; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this guy is the one that manages all backups across all workspaces and the vs/platform/backup is the one per workspace for individual files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IBackupService
manages all I/O with what is now theIEnvironmentService.backupHome
directory.