提交 bb55f060 编写于 作者: B Benjamin Pasero

mr - migrate backups when entering workspace

上级 1de0401b
......@@ -511,10 +511,14 @@ export class CodeWindow implements ICodeWindow {
}
}
public reload(cli?: ParsedArgs): void {
public reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
// Inherit current properties but overwrite some
const configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
// If config is not provided, copy our current one
if (!configuration) {
configuration = objects.mixin({}, this.currentConfig);
}
// Delete some properties we do not want during reload
delete configuration.filesToOpen;
delete configuration.filesToCreate;
delete configuration.filesToDiff;
......
......@@ -19,7 +19,7 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/pa
import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows';
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState } from 'vs/platform/windows/common/windows';
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/node/product';
......@@ -937,16 +937,16 @@ export class WindowsManager implements IWindowsMainService {
configuration.backupPath = path.join(this.environmentService.backupHome, options.emptyWindowBackupFolder);
}
let codeWindow: CodeWindow;
let window: CodeWindow;
if (!options.forceNewWindow) {
codeWindow = options.windowToUse || this.getLastActiveWindow();
if (codeWindow) {
codeWindow.focus();
window = options.windowToUse || this.getLastActiveWindow();
if (window) {
window.focus();
}
}
// New window
if (!codeWindow) {
if (!window) {
const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
const state = this.getNewWindowState(configuration);
......@@ -965,27 +965,27 @@ export class WindowsManager implements IWindowsMainService {
state.mode = WindowMode.Normal;
}
codeWindow = this.instantiationService.createInstance(CodeWindow, {
window = this.instantiationService.createInstance(CodeWindow, {
state,
extensionDevelopmentPath: configuration.extensionDevelopmentPath,
isExtensionTestHost: !!configuration.extensionTestsPath
});
// Add to our list of windows
WindowsManager.WINDOWS.push(codeWindow);
WindowsManager.WINDOWS.push(window);
// Indicate number change via event
this._onWindowsCountChanged.fire({ oldCount: WindowsManager.WINDOWS.length - 1, newCount: WindowsManager.WINDOWS.length });
// Window Events
codeWindow.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
codeWindow.win.webContents.on('devtools-reload-page', () => this.reload(codeWindow));
codeWindow.win.webContents.on('crashed', () => this.onWindowError(codeWindow, WindowError.CRASHED));
codeWindow.win.on('unresponsive', () => this.onWindowError(codeWindow, WindowError.UNRESPONSIVE));
codeWindow.win.on('closed', () => this.onWindowClosed(codeWindow));
window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own
window.win.webContents.on('devtools-reload-page', () => this.reload(window));
window.win.webContents.on('crashed', () => this.onWindowError(window, WindowError.CRASHED));
window.win.on('unresponsive', () => this.onWindowError(window, WindowError.UNRESPONSIVE));
window.win.on('closed', () => this.onWindowClosed(window));
// Lifecycle
this.lifecycleService.registerWindow(codeWindow);
this.lifecycleService.registerWindow(window);
}
// Existing window
......@@ -993,7 +993,7 @@ export class WindowsManager implements IWindowsMainService {
// Some configuration things get inherited if the window is being reused and we are
// in extension development host mode. These options are all development related.
const currentWindowConfig = codeWindow.config;
const currentWindowConfig = window.config;
if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) {
configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath;
configuration.verbose = currentWindowConfig.verbose;
......@@ -1005,7 +1005,7 @@ export class WindowsManager implements IWindowsMainService {
}
// Only load when the window has not vetoed this
this.lifecycleService.unload(codeWindow, UnloadReason.LOAD).done(veto => {
this.lifecycleService.unload(window, UnloadReason.LOAD).done(veto => {
if (!veto) {
// Register window for backups
......@@ -1020,11 +1020,11 @@ export class WindowsManager implements IWindowsMainService {
}
// Load it
codeWindow.load(configuration);
window.load(configuration);
}
});
return codeWindow;
return window;
}
private getNewWindowState(configuration: IWindowConfiguration): INewWindowState {
......@@ -1156,7 +1156,7 @@ export class WindowsManager implements IWindowsMainService {
// Only reload when the window has not vetoed this
this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
if (!veto) {
win.reload(cli);
win.reload(void 0, cli);
// Emit
this._onWindowReload.fire(win.id);
......@@ -1171,6 +1171,46 @@ export class WindowsManager implements IWindowsMainService {
});
}
public createAndOpenWorkspace(window: CodeWindow, folders?: string[], path?: string): TPromise<void> {
if (!window || !window.win || window.readyState !== ReadyState.READY) {
return TPromise.as(null); // return early if the window is not ready or disposed
}
return this.workspacesService.createWorkspace(folders).then(workspace => {
let savePromise: TPromise<IWorkspaceIdentifier>;
if (path) {
savePromise = this.workspacesService.saveWorkspace(workspace, path);
} else {
savePromise = TPromise.as(workspace);
}
return savePromise.then(workspace => {
window.focus();
// Only open workspace when the window has not vetoed this
return this.lifecycleService.unload(window, UnloadReason.RELOAD).done(veto => {
if (!veto) {
// Register window for backups and migrate current backups over
let backupPath: string;
if (window.config && !window.config.extensionDevelopmentPath) {
backupPath = this.backupService.registerWorkspaceBackupSync(workspace, window.config.backupPath);
}
// Craft a new window configuration to use for the transition
const configuration: IWindowConfiguration = mixin({}, window.config);
configuration.folderPath = void 0;
configuration.workspace = workspace;
configuration.backupPath = backupPath;
// Reload
window.reload(configuration);
}
});
});
});
}
public openWorkspace(window: CodeWindow = this.getLastActiveWindow()): void {
let defaultPath: string;
if (window && window.openedWorkspace && !this.workspacesService.isUntitledWorkspace(window.openedWorkspace)) {
......@@ -1380,12 +1420,12 @@ export class WindowsManager implements IWindowsMainService {
return WindowsManager.WINDOWS.length;
}
private onWindowError(codeWindow: CodeWindow, error: WindowError): void {
private onWindowError(window: CodeWindow, error: WindowError): void {
this.logService.error(error === WindowError.CRASHED ? '[VS Code]: render process crashed!' : '[VS Code]: detected unresponsive');
// Unresponsive
if (error === WindowError.UNRESPONSIVE) {
dialog.showMessageBox(codeWindow.win, {
dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
buttons: [localize('reopen', "Reopen"), localize('wait', "Keep Waiting"), localize('close', "Close")],
......@@ -1393,22 +1433,22 @@ export class WindowsManager implements IWindowsMainService {
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
noLink: true
}, result => {
if (!codeWindow.win) {
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
codeWindow.reload();
window.reload();
} else if (result === 2) {
this.onBeforeWindowClose(codeWindow); // 'close' event will not be fired on destroy(), so run it manually
codeWindow.win.destroy(); // make sure to destroy the window as it is unresponsive
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it is unresponsive
}
});
}
// Crashed
else {
dialog.showMessageBox(codeWindow.win, {
dialog.showMessageBox(window.win, {
title: product.nameLong,
type: 'warning',
buttons: [localize('reopen', "Reopen"), localize('close', "Close")],
......@@ -1416,15 +1456,15 @@ export class WindowsManager implements IWindowsMainService {
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
noLink: true
}, result => {
if (!codeWindow.win) {
if (!window.win) {
return; // Return early if the window has been going down already
}
if (result === 0) {
codeWindow.reload();
window.reload();
} else if (result === 1) {
this.onBeforeWindowClose(codeWindow); // 'close' event will not be fired on destroy(), so run it manually
codeWindow.win.destroy(); // make sure to destroy the window as it has crashed
this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually
window.win.destroy(); // make sure to destroy the window as it has crashed
}
});
}
......@@ -1493,9 +1533,9 @@ export class WindowsManager implements IWindowsMainService {
// If the user selected to exit from an extension development host window, do not quit, but just
// close the window unless this is the last window that is opened.
const codeWindow = this.getFocusedWindow();
if (codeWindow && codeWindow.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
codeWindow.win.close();
const window = this.getFocusedWindow();
if (window && window.isExtensionDevelopmentHost && this.getWindowCount() > 1) {
window.win.close();
}
// Otherwise: normal quit
......
......@@ -23,7 +23,7 @@ export interface IBackupMainService {
getFolderBackupPaths(): string[];
getEmptyWindowBackupPaths(): string[];
registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier): string;
registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string;
registerFolderBackupSync(folderPath: string): string;
registerEmptyWindowBackupSync(backupFolder?: string): string;
}
\ No newline at end of file
......@@ -97,10 +97,28 @@ export class BackupMainService implements IBackupMainService {
return this.backups.emptyWorkspaces.slice(0); // return a copy
}
public registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier): string {
public registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string {
this.pushBackupPathsSync(workspace, this.backups.rootWorkspaces);
return path.join(this.backupHome, workspace.id);
const backupPath = path.join(this.backupHome, workspace.id);
if (migrateFrom) {
this.moveBackupFolderSync(backupPath, migrateFrom);
}
return backupPath;
}
private moveBackupFolderSync(backupPath: string, moveFromPath: string): void {
if (!fs.existsSync(moveFromPath)) {
return;
}
try {
fs.renameSync(moveFromPath, backupPath);
} catch (ex) {
this.logService.error(`Backup: Could not move backup folder to new location: ${ex.toString()}`);
}
}
public registerFolderBackupSync(folderPath: string): string {
......
......@@ -196,6 +196,21 @@ suite('BackupMainService', () => {
done();
});
test('service supports to migrate backup data from another location', done => {
const backupPathToMigrate = service.toBackupPath(fooFile.fsPath);
fs.mkdirSync(backupPathToMigrate);
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
service.registerFolderBackupSync(backupPathToMigrate);
const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath), backupPathToMigrate);
assert.ok(fs.existsSync(workspaceBackupPath));
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
assert.ok(!fs.existsSync(backupPathToMigrate));
done();
});
suite('loadSync', () => {
test('getFolderBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.getFolderBackupPaths(), []);
......
......@@ -59,9 +59,9 @@ export interface ILifecycleService {
onBeforeWindowUnload: Event<IWindowUnloadEvent>;
ready(): void;
registerWindow(codeWindow: ICodeWindow): void;
registerWindow(window: ICodeWindow): void;
unload(codeWindow: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
unload(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
relaunch(options?: { addArgs?: string[], removeArgs?: string[] });
......@@ -148,11 +148,11 @@ export class LifecycleService implements ILifecycleService {
});
}
public registerWindow(codeWindow: ICodeWindow): void {
public registerWindow(window: ICodeWindow): void {
// Window Before Closing: Main -> Renderer
codeWindow.win.on('close', e => {
const windowId = codeWindow.id;
window.win.on('close', e => {
const windowId = window.id;
this.logService.log('Lifecycle#window-before-close', windowId);
// The window already acknowledged to be closed
......@@ -166,11 +166,11 @@ export class LifecycleService implements ILifecycleService {
// Otherwise prevent unload and handle it from window
e.preventDefault();
this.unload(codeWindow, UnloadReason.CLOSE).done(veto => {
this.unload(window, UnloadReason.CLOSE).done(veto => {
if (!veto) {
this.windowToCloseRequest[windowId] = true;
this._onBeforeWindowClose.fire(codeWindow);
codeWindow.close();
this._onBeforeWindowClose.fire(window);
window.close();
} else {
this.quitRequested = false;
delete this.windowToCloseRequest[windowId];
......@@ -179,25 +179,25 @@ export class LifecycleService implements ILifecycleService {
});
}
public unload(codeWindow: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
public unload(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
// Always allow to unload a window that is not yet ready
if (codeWindow.readyState !== ReadyState.READY) {
if (window.readyState !== ReadyState.READY) {
return TPromise.as<boolean>(false);
}
this.logService.log('Lifecycle#unload()', codeWindow.id);
this.logService.log('Lifecycle#unload()', window.id);
const windowUnloadReason = this.quitRequested ? UnloadReason.QUIT : reason;
// first ask the window itself if it vetos the unload
return this.doUnloadWindowInRenderer(codeWindow, windowUnloadReason).then(veto => {
return this.doUnloadWindowInRenderer(window, windowUnloadReason).then(veto => {
if (veto) {
return this.handleVeto(veto);
}
// then check for vetos in the main side
return this.doUnloadWindowInMain(codeWindow, windowUnloadReason).then(veto => this.handleVeto(veto));
return this.doUnloadWindowInMain(window, windowUnloadReason).then(veto => this.handleVeto(veto));
});
}
......@@ -213,7 +213,7 @@ export class LifecycleService implements ILifecycleService {
return veto;
}
private doUnloadWindowInRenderer(codeWindow: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
private doUnloadWindowInRenderer(window: ICodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
return new TPromise<boolean>((c) => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const okChannel = `vscode:ok${oneTimeEventToken}`;
......@@ -227,7 +227,7 @@ export class LifecycleService implements ILifecycleService {
c(true); // veto
});
codeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason });
window.send('vscode:beforeUnload', { okChannel, cancelChannel, reason });
});
}
......
......@@ -42,6 +42,7 @@ export interface IWindowsService {
toggleDevTools(windowId: number): TPromise<void>;
closeWorkspace(windowId: number): TPromise<void>;
openWorkspace(windowId: number): TPromise<void>;
createAndOpenWorkspace(windowId: number, folders?: string[], path?: string): TPromise<void>;
toggleFullScreen(windowId: number): TPromise<void>;
setRepresentedFilename(windowId: number, fileName: string): TPromise<void>;
addRecentlyOpened(files: string[]): TPromise<void>;
......@@ -97,6 +98,7 @@ export interface IWindowService {
toggleDevTools(): TPromise<void>;
closeWorkspace(): TPromise<void>;
openWorkspace(): TPromise<void>;
createAndOpenWorkspace(folders?: string[], path?: string): TPromise<void>;
toggleFullScreen(): TPromise<void>;
setRepresentedFilename(fileName: string): TPromise<void>;
getRecentlyOpened(): TPromise<IRecentlyOpened>;
......
......@@ -23,6 +23,7 @@ export interface IWindowsChannel extends IChannel {
call(command: 'toggleDevTools', arg: number): TPromise<void>;
call(command: 'closeWorkspace', arg: number): TPromise<void>;
call(command: 'openWorkspace', arg: number): TPromise<void>;
call(command: 'createAndOpenWorkspace', arg: [number, string[], string]): TPromise<void>;
call(command: 'toggleFullScreen', arg: number): TPromise<void>;
call(command: 'setRepresentedFilename', arg: [number, string]): TPromise<void>;
call(command: 'addRecentlyOpened', arg: string[]): TPromise<void>;
......@@ -78,6 +79,7 @@ export class WindowsChannel implements IWindowsChannel {
case 'toggleDevTools': return this.service.toggleDevTools(arg);
case 'closeWorkspace': return this.service.closeWorkspace(arg);
case 'openWorkspace': return this.service.openWorkspace(arg);
case 'createAndOpenWorkspace': return this.service.createAndOpenWorkspace(arg[0], arg[1], arg[2]);
case 'toggleFullScreen': return this.service.toggleFullScreen(arg);
case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]);
case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg);
......@@ -157,6 +159,10 @@ export class WindowsChannelClient implements IWindowsService {
return this.channel.call('openWorkspace', windowId);
}
createAndOpenWorkspace(windowId: number, folders?: string[], path?: string): TPromise<void> {
return this.channel.call('createAndOpenWorkspace', [windowId, folders, path]);
}
toggleFullScreen(windowId: number): TPromise<void> {
return this.channel.call('toggleFullScreen', windowId);
}
......
......@@ -68,6 +68,10 @@ export class WindowService implements IWindowService {
return this.windowsService.openWorkspace(this.windowId);
}
createAndOpenWorkspace(folders?: string[], path?: string): TPromise<void> {
return this.windowsService.createAndOpenWorkspace(this.windowId, folders, path);
}
closeWindow(): TPromise<void> {
return this.windowsService.closeWindow(this.windowId);
}
......
......@@ -58,6 +58,7 @@ export interface IWindowsMainService {
ready(initialUserEnv: IProcessEnvironment): void;
reload(win: ICodeWindow, cli?: ParsedArgs): void;
openWorkspace(win?: ICodeWindow): void;
createAndOpenWorkspace(win: ICodeWindow, folders?: string[], path?: string): void;
closeWorkspace(win: ICodeWindow): void;
open(openConfig: IOpenConfiguration): ICodeWindow[];
openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void;
......
......@@ -124,6 +124,16 @@ export class WindowsService implements IWindowsService, IDisposable {
return TPromise.as(null);
}
createAndOpenWorkspace(windowId: number, folders?: string[], path?: string): TPromise<void> {
const codeWindow = this.windowsMainService.getWindowById(windowId);
if (codeWindow) {
this.windowsMainService.createAndOpenWorkspace(codeWindow, folders, path);
}
return TPromise.as(null);
}
toggleFullScreen(windowId: number): TPromise<void> {
const codeWindow = this.windowsMainService.getWindowById(windowId);
......
......@@ -186,8 +186,9 @@ class NewWorkspaceAction extends BaseWorkspacesAction {
}
private createWorkspace(folders: URI[]): TPromise<void> {
return this.workspacesService.createWorkspace(distinct(folders.map(folder => folder.toString(true /* encoding */))))
.then(({ configPath }) => this.windowsService.openWindow([configPath]));
const workspaceFolders = distinct(folders.map(folder => folder.toString(true /* encoding */)));
return this.windowService.createAndOpenWorkspace(workspaceFolders);
}
}
......@@ -250,14 +251,9 @@ export class SaveWorkspaceAsAction extends BaseWorkspacesAction {
private saveFolderWorkspace(configPath: string): TPromise<void> {
if (this.handleNotInMultiFolderWorkspaceCase(nls.localize('saveNotSupported', "To save workspace, window reload is required."))) {
// Create workspace first
this.workspacesService.createWorkspace(this.contextService.getWorkspace().roots.map(root => root.toString(true /* skip encoding */)))
.then(workspaceIdentifier => {
// Save the workspace in new location
return this.workspacesService.saveWorkspace(workspaceIdentifier, configPath)
// Open the saved workspace
.then(({ configPath }) => this.windowsService.openWindow([configPath]));
});
const workspaceFolders = this.contextService.getWorkspace().roots.map(root => root.toString(true /* skip encoding */));
return this.windowService.createAndOpenWorkspace(workspaceFolders, configPath);
}
return TPromise.as(null);
......
......@@ -882,6 +882,10 @@ export class TestWindowService implements IWindowService {
return TPromise.as(void 0);
}
createAndOpenWorkspace(folders?: string[], path?: string): TPromise<void> {
return TPromise.as(void 0);
}
toggleFullScreen(): TPromise<void> {
return TPromise.as(void 0);
}
......@@ -1014,6 +1018,10 @@ export class TestWindowsService implements IWindowsService {
return TPromise.as(void 0);
}
createAndOpenWorkspace(windowId: number, folders?: string[], path?: string): TPromise<void> {
return TPromise.as(void 0);
}
toggleFullScreen(windowId: number): TPromise<void> {
return TPromise.as(void 0);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册