提交 5aeba8bf 编写于 作者: B Benjamin Pasero 提交者: GitHub

Merge pull request #16198 from Microsoft/ben/hot-exit-reload

introduce shutdown reason (fixes #15509)
......@@ -16,6 +16,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleService');
export enum UnloadReason {
CLOSE,
QUIT,
RELOAD,
LOAD
}
export interface ILifecycleService {
_serviceBrand: any;
......@@ -33,7 +40,7 @@ export interface ILifecycleService {
ready(): void;
registerWindow(vscodeWindow: IVSCodeWindow): void;
unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */>;
unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */>;
quit(fromUpdate?: boolean): TPromise<boolean /* veto */>;
}
......@@ -126,7 +133,7 @@ export class LifecycleService implements ILifecycleService {
// Otherwise prevent unload and handle it from window
e.preventDefault();
this.unload(vscodeWindow).done(veto => {
this.unload(vscodeWindow, UnloadReason.CLOSE).done(veto => {
if (!veto) {
this.windowToCloseRequest[windowId] = true;
vscodeWindow.win.close();
......@@ -138,7 +145,7 @@ export class LifecycleService implements ILifecycleService {
});
}
public unload(vscodeWindow: IVSCodeWindow): TPromise<boolean /* veto */> {
public unload(vscodeWindow: IVSCodeWindow, reason: UnloadReason): TPromise<boolean /* veto */> {
// Always allow to unload a window that is not yet ready
if (vscodeWindow.readyState !== ReadyState.READY) {
......@@ -149,14 +156,14 @@ export class LifecycleService implements ILifecycleService {
return new TPromise<boolean>((c) => {
const oneTimeEventToken = this.oneTimeListenerTokenGenerator++;
const oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken;
const oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken;
const okChannel = `vscode:ok${oneTimeEventToken}`;
const cancelChannel = `vscode:cancel${oneTimeEventToken}`;
ipc.once(oneTimeOkEvent, () => {
ipc.once(okChannel, () => {
c(false); // no veto
});
ipc.once(oneTimeCancelEvent, () => {
ipc.once(cancelChannel, () => {
// Any cancellation also cancels a pending quit if present
if (this.pendingQuitPromiseComplete) {
......@@ -168,7 +175,7 @@ export class LifecycleService implements ILifecycleService {
c(true); // veto
});
vscodeWindow.send('vscode:beforeUnload', { okChannel: oneTimeOkEvent, cancelChannel: oneTimeCancelEvent, quitRequested: this.quitRequested });
vscodeWindow.send('vscode:beforeUnload', { okChannel, cancelChannel, reason: this.quitRequested ? UnloadReason.QUIT : reason });
});
}
......
......@@ -20,7 +20,7 @@ import { IStorageService } from 'vs/code/electron-main/storage';
import { IPath, VSCodeWindow, IWindowConfiguration, IWindowState as ISingleWindowState, defaultWindowState, ReadyState } from 'vs/code/electron-main/window';
import { ipcMain as ipc, app, screen, BrowserWindow, dialog } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/electron-main/paths';
import { ILifecycleService } from 'vs/code/electron-main/lifecycle';
import { ILifecycleService, UnloadReason } from 'vs/code/electron-main/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/code/electron-main/log';
import { getPathLabel } from 'vs/base/common/labels';
......@@ -273,7 +273,7 @@ export class WindowsManager implements IWindowsMainService {
public reload(win: VSCodeWindow, cli?: ParsedArgs): void {
// Only reload when the window has not vetoed this
this.lifecycleService.unload(win).done(veto => {
this.lifecycleService.unload(win, UnloadReason.RELOAD).done(veto => {
if (!veto) {
win.reload(cli);
}
......@@ -762,7 +762,7 @@ export class WindowsManager implements IWindowsMainService {
}
// Only load when the window has not vetoed this
this.lifecycleService.unload(vscodeWindow).done(veto => {
this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => {
if (!veto) {
// Load it
......
......@@ -20,7 +20,22 @@ export const ILifecycleService = createDecorator<ILifecycleService>('lifecycleSe
*/
export interface ShutdownEvent {
veto(value: boolean | TPromise<boolean>): void;
quitRequested: boolean;
reason: ShutdownReason;
}
export enum ShutdownReason {
/** Window is closed */
CLOSE,
/** Application is quit */
QUIT,
/** Window is reloaded */
RELOAD,
/** Other configuration loaded into window */
LOAD
}
/**
......@@ -37,12 +52,6 @@ export interface ILifecycleService {
*/
willShutdown: boolean;
/**
* A flag indications if the application is in the process of quitting all windows. This will be
* set before the onWillShutdown event is fired and reverted to false afterwards.
*/
quitRequested: boolean;
/**
* Fired before shutdown happens. Allows listeners to veto against the
* shutdown.
......@@ -59,7 +68,6 @@ export interface ILifecycleService {
export const NullLifecycleService: ILifecycleService = {
_serviceBrand: null,
willShutdown: false,
quitRequested: false,
onWillShutdown: () => ({ dispose() { } }),
onShutdown: () => ({ dispose() { } })
};
\ No newline at end of file
......@@ -708,7 +708,6 @@ export class TestLifecycleService implements ILifecycleService {
public _serviceBrand: any;
public willShutdown: boolean;
public quitRequested: boolean;
private _onWillShutdown = new Emitter<ShutdownEvent>();
private _onShutdown = new Emitter<void>();
......
......@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { TPromise } from 'vs/base/common/winjs.base';
import { ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles';
import { IResolveContentOptions, IUpdateContentOptions } from 'vs/platform/files/common/files';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export const IBackupService = createDecorator<IBackupService>('backupService');
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
......@@ -29,7 +30,7 @@ export interface IBackupService {
_serviceBrand: any;
isHotExitEnabled: boolean;
backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult>;
backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult>;
cleanupBackupsBeforeShutdown(): TPromise<void>;
}
......
......@@ -17,6 +17,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { TPromise } from 'vs/base/common/winjs.base';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export class BackupService implements IBackupService {
......@@ -95,21 +96,46 @@ export class BackupService implements IBackupService {
return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace();
}
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, quitRequested: boolean): TPromise<IBackupResult> {
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult> {
if (!this.isHotExitEnabled) {
return TPromise.as({ didBackup: false });
}
return this.windowsService.getWindowCount().then(windowCount => {
// When quit is requested skip the confirm callback and attempt to backup all workspaces.
// When quit is not requested the confirm callback should be shown when the window being
// closed is the only VS Code window open, except for on Mac where hot exit is only
// ever activated when quit is requested.
if (!quitRequested && (windowCount > 1 || platform.isMacintosh)) {
let doBackup: boolean;
switch (reason) {
case ShutdownReason.CLOSE:
if (windowCount > 1 || platform.isMacintosh) {
doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
} else {
doBackup = true; // backup if last window is closed on win/linux where the application quits right after
}
break;
case ShutdownReason.QUIT:
doBackup = true; // backup because next start we restore all backups
break;
case ShutdownReason.RELOAD:
doBackup = true; // backup because after window reload, backups restore
break;
case ShutdownReason.LOAD:
doBackup = false; // do not backup because we are switching contexts
break;
}
if (!doBackup) {
return TPromise.as({ didBackup: false });
}
// Backup and hot exit
// Backup
return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; }); // we did backup
});
}
......
......@@ -7,7 +7,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import Severity from 'vs/base/common/severity';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { ipcRenderer as ipc } from 'electron';
......@@ -21,7 +21,6 @@ export class LifecycleService implements ILifecycleService {
private _onShutdown = new Emitter<void>();
private _willShutdown: boolean;
private _quitRequested: boolean;
constructor(
@IMessageService private messageService: IMessageService,
......@@ -34,10 +33,6 @@ export class LifecycleService implements ILifecycleService {
return this._willShutdown;
}
public get quitRequested(): boolean {
return this._quitRequested;
}
public get onWillShutdown(): Event<ShutdownEvent> {
return this._onWillShutdown.event;
}
......@@ -50,13 +45,11 @@ export class LifecycleService implements ILifecycleService {
const windowId = this.windowService.getWindowId();
// Main side indicates that window is about to unload, check for vetos
ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, quitRequested: boolean }) => {
ipc.on('vscode:beforeUnload', (event, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => {
this._willShutdown = true;
this._quitRequested = reply.quitRequested;
// trigger onWillShutdown events and veto collecting
this.onBeforeUnload(reply.quitRequested).done(veto => {
this._quitRequested = false;
this.onBeforeUnload(reply.reason).done(veto => {
if (veto) {
this._willShutdown = false; // reset this flag since the shutdown has been vetoed!
ipc.send(reply.cancelChannel, windowId);
......@@ -68,14 +61,14 @@ export class LifecycleService implements ILifecycleService {
});
}
private onBeforeUnload(quitRequested: boolean): TPromise<boolean> {
private onBeforeUnload(reason: ShutdownReason): TPromise<boolean> {
const vetos: (boolean | TPromise<boolean>)[] = [];
this._onWillShutdown.fire({
veto(value) {
vetos.push(value);
},
quitRequested
reason
});
if (vetos.length === 0) {
......
......@@ -14,7 +14,7 @@ import objects = require('vs/base/common/objects');
import Event, { Emitter } from 'vs/base/common/event';
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult } from 'vs/workbench/common/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
......@@ -101,7 +101,7 @@ export abstract class TextFileService implements ITextFileService {
private registerListeners(): void {
// Lifecycle
this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.quitRequested)));
this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown(event.reason)));
this.lifecycleService.onShutdown(this.dispose, this);
// Configuration changes
......@@ -113,7 +113,7 @@ export abstract class TextFileService implements ITextFileService {
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged()));
}
private beforeShutdown(quitRequested: boolean): boolean | TPromise<boolean> {
private beforeShutdown(reason: ShutdownReason): boolean | TPromise<boolean> {
// Dirty files need treatment on shutdown
const dirty = this.getDirty();
......@@ -135,7 +135,7 @@ export abstract class TextFileService implements ITextFileService {
// If hot exit is enabled, backup dirty files and allow to exit without confirmation
if (this.backupService.isHotExitEnabled) {
return this.backupService.backupBeforeShutdown(dirty, this.models, quitRequested).then(result => {
return this.backupService.backupBeforeShutdown(dirty, this.models, reason).then(result => {
if (result.didBackup) {
return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful)
}
......
......@@ -6,7 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import * as assert from 'assert';
import { ILifecycleService, ShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle';
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, onError, toResource } from 'vs/test/utils/servicesTestUtils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
......@@ -28,7 +28,7 @@ class ServiceAccessor {
class ShutdownEventImpl implements ShutdownEvent {
public value: boolean | TPromise<boolean>;
public quitRequested: boolean = false;
public reason = ShutdownReason.CLOSE;
veto(value: boolean | TPromise<boolean>): void {
this.value = value;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册