提交 d5349d2b 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #16303 from Microsoft/tyriar/hot_exit/empty_workspaces

Hot exit empty workspace support
......@@ -35,6 +35,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, MainLogService } from 'vs/code/electron-main/log';
import { IStorageService, StorageService } from 'vs/code/electron-main/storage';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { BackupChannel } from 'vs/platform/backup/common/backupIpc';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
......@@ -185,6 +186,10 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo
const urlChannel = instantiationService2.createInstance(URLChannel, urlService);
electronIpcServer.registerChannel('url', urlChannel);
const backupService = accessor.get(IBackupMainService);
const backupChannel = instantiationService2.createInstance(BackupChannel, backupService);
electronIpcServer.registerChannel('backup', backupChannel);
const windowsService = accessor.get(IWindowsService);
const windowsChannel = new WindowsChannel(windowsService);
electronIpcServer.registerChannel('windows', windowsChannel);
......
......@@ -29,7 +29,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowSettings } from 'vs/platform/windows/common/windows';
import CommonEvent, { Emitter } from 'vs/base/common/event';
import product from 'vs/platform/product';
import Uri from 'vs/base/common/uri';
enum WindowError {
UNRESPONSIVE,
......@@ -331,10 +330,11 @@ export class WindowsManager implements IWindowsMainService {
}
let foldersToOpen = arrays.distinct(iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath).map(iPath => iPath.workspacePath), folder => platform.isLinux ? folder : folder.toLowerCase()); // prevent duplicates
let foldersToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.getWorkspaceBackupPaths() : [];
let foldersToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.workspaceBackupPaths : [];
let filesToOpen: IPath[] = [];
let filesToDiff: IPath[] = [];
let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath);
let emptyToRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath) ? this.backupService.emptyWorkspaceBackupPaths : [];
let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath);
// Diff mode needs special care
......@@ -441,7 +441,17 @@ export class WindowsManager implements IWindowsMainService {
}
// Handle empty
if (emptyToOpen.length > 0) {
if (emptyToRestore.length > 0) {
emptyToRestore.forEach(emptyWorkspaceBackupFolder => {
const configuration = this.toConfiguration(openConfig, null, null, null, null);
const browserWindow = this.openInBrowserWindow(configuration, true /* new window */, null, emptyWorkspaceBackupFolder);
usedWindows.push(browserWindow);
openInNewWindow = true; // any other folders to open must open in new window then
});
}
// Only open empty if no empty workspaces were restored
else if (emptyToOpen.length > 0) {
emptyToOpen.forEach(() => {
const configuration = this.toConfiguration(openConfig);
const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse);
......@@ -468,10 +478,6 @@ export class WindowsManager implements IWindowsMainService {
}
}
// Register new paths for backup
if (!openConfig.cli.extensionDevelopmentPath) {
this.backupService.pushWorkspaceBackupPathsSync(iPathsToOpen.filter(p => p.workspacePath).map(p => Uri.file(p.workspacePath)));
}
// Emit events
this._onPathsOpen.fire(iPathsToOpen);
......@@ -711,7 +717,7 @@ export class WindowsManager implements IWindowsMainService {
return [Object.create(null)];
}
private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow): VSCodeWindow {
private openInBrowserWindow(configuration: IWindowConfiguration, forceNewWindow?: boolean, windowToUse?: VSCodeWindow, emptyWorkspaceBackupFolder?: string): VSCodeWindow {
let vscodeWindow: VSCodeWindow;
if (!forceNewWindow) {
......@@ -762,6 +768,10 @@ export class WindowsManager implements IWindowsMainService {
}
}
if (!configuration.extensionDevelopmentPath) {
this.backupService.registerWindowForBackups(vscodeWindow.id, !configuration.workspacePath, emptyWorkspaceBackupFolder, configuration.workspacePath);
}
// Only load when the window has not vetoed this
this.lifecycleService.unload(vscodeWindow, UnloadReason.LOAD).done(veto => {
if (!veto) {
......
......@@ -4,28 +4,27 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Uri from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
export interface IBackupWorkspacesFormat {
folderWorkspaces: string[];
emptyWorkspaces: string[];
}
export const IBackupMainService = createDecorator<IBackupMainService>('backupService');
export const IBackupMainService = createDecorator<IBackupMainService>('backupMainService');
export const IBackupService = createDecorator<IBackupService>('backupService');
export interface IBackupMainService {
export interface IBackupMainService extends IBackupService {
_serviceBrand: any;
/**
* Gets the set of active workspace backup paths being tracked for restoration.
*
* @return The set of active workspace backup paths being tracked for restoration.
*/
getWorkspaceBackupPaths(): string[];
/**
* Pushes workspace backup paths to be tracked for restoration.
*
* @param workspaces The workspaces to add.
*/
pushWorkspaceBackupPathsSync(workspaces: Uri[]): void;
workspaceBackupPaths: string[];
emptyWorkspaceBackupPaths: string[];
registerWindowForBackups(windowId: number, isEmptyWorkspace: boolean, backupFolder?: string, workspacePath?: string);
}
export interface IBackupService {
_serviceBrand: any;
getBackupPath(windowId: number): TPromise<string>;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IBackupService } from './backup';
export interface IBackupChannel extends IChannel {
call(command: 'getBackupPath', arg: [number]): TPromise<string>;
call(command: string, arg?: any): TPromise<any>;
}
export class BackupChannel implements IBackupChannel {
constructor(private service: IBackupService) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'getBackupPath': return this.service.getBackupPath(arg);
}
}
}
export class BackupChannelClient implements IBackupService {
_serviceBrand: any;
constructor(private channel: IBackupChannel) { }
getBackupPath(windowId: number): TPromise<string> {
return this.channel.call('getBackupPath', windowId);
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
......@@ -11,6 +12,7 @@ import * as extfs from 'vs/base/node/extfs';
import Uri from 'vs/base/common/uri';
import { IBackupWorkspacesFormat, IBackupMainService } from 'vs/platform/backup/common/backup';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TPromise } from 'vs/base/common/winjs.base';
export class BackupMainService implements IBackupMainService {
......@@ -21,42 +23,65 @@ export class BackupMainService implements IBackupMainService {
private backups: IBackupWorkspacesFormat;
protected mapWindowToBackupFolder: { [windowId: number]: string; };
constructor(
@IEnvironmentService environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService
) {
this.backupHome = environmentService.backupHome;
this.workspacesJsonPath = environmentService.backupWorkspacesPath;
this.mapWindowToBackupFolder = Object.create(null);
this.loadSync();
}
public getWorkspaceBackupPaths(): string[] {
public get workspaceBackupPaths(): string[] {
return this.backups.folderWorkspaces;
}
public pushWorkspaceBackupPathsSync(workspaces: Uri[]): void {
let needsSaving = false;
workspaces.forEach(workspace => {
if (this.backups.folderWorkspaces.indexOf(workspace.fsPath) === -1) {
this.backups.folderWorkspaces.push(workspace.fsPath);
needsSaving = true;
}
});
public get emptyWorkspaceBackupPaths(): string[] {
return this.backups.emptyWorkspaces;
}
if (needsSaving) {
public getBackupPath(windowId: number): TPromise<string> {
if (!this.mapWindowToBackupFolder[windowId]) {
throw new Error(`Unknown backup workspace for window ${windowId}`);
}
return TPromise.as(path.join(this.backupHome, this.mapWindowToBackupFolder[windowId]));
}
public registerWindowForBackups(windowId: number, isEmptyWorkspace: boolean, backupFolder?: string, workspacePath?: string): void {
// Generate a new folder if this is a new empty workspace
if (isEmptyWorkspace && !backupFolder) {
backupFolder = Date.now().toString();
}
this.mapWindowToBackupFolder[windowId] = isEmptyWorkspace ? backupFolder : this.getWorkspaceHash(workspacePath);
this.pushBackupPathsSync(isEmptyWorkspace ? backupFolder : workspacePath, isEmptyWorkspace);
}
protected pushBackupPathsSync(workspaceIdentifier: string, isEmptyWorkspace: boolean): void {
if (!isEmptyWorkspace) {
workspaceIdentifier = this.sanitizePath(workspaceIdentifier);
}
const array = isEmptyWorkspace ? this.backups.emptyWorkspaces : this.backups.folderWorkspaces;
if (array.indexOf(workspaceIdentifier) === -1) {
array.push(workspaceIdentifier);
this.saveSync();
}
}
protected removeWorkspaceBackupPathSync(workspace: Uri): void {
if (!this.backups.folderWorkspaces) {
protected removeBackupPathSync(workspaceIdentifier: string, isEmptyWorkspace: boolean): void {
const array = isEmptyWorkspace ? this.backups.emptyWorkspaces : this.backups.folderWorkspaces;
if (!array) {
return;
}
const index = this.backups.folderWorkspaces.indexOf(workspace.fsPath);
const index = array.indexOf(workspaceIdentifier);
if (index === -1) {
return;
}
this.backups.folderWorkspaces.splice(index, 1);
array.splice(index, 1);
this.saveSync();
}
......@@ -72,12 +97,20 @@ export class BackupMainService implements IBackupMainService {
if (backups.folderWorkspaces) {
const fws = backups.folderWorkspaces;
if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) {
backups = Object.create(null);
backups.folderWorkspaces = [];
}
} else {
backups.folderWorkspaces = [];
}
if (!backups.folderWorkspaces) {
backups.folderWorkspaces = [];
// Ensure emptyWorkspaces is a string[]
if (backups.emptyWorkspaces) {
const fws = backups.emptyWorkspaces;
if (!Array.isArray(fws) || fws.some(f => typeof f !== 'string')) {
backups.emptyWorkspaces = [];
}
} else {
backups.emptyWorkspaces = [];
}
this.backups = backups;
......@@ -86,21 +119,36 @@ export class BackupMainService implements IBackupMainService {
this.validateBackupWorkspaces(backups);
}
protected sanitizeFolderWorkspaces(backups: IBackupWorkspacesFormat): void {
// Merge duplicates for folder workspaces, don't worry about cleaning them up as they will
// be removed when there are no backups.
backups.folderWorkspaces = arrays.distinct(backups.folderWorkspaces.map(w => this.sanitizePath(w)));
}
private validateBackupWorkspaces(backups: IBackupWorkspacesFormat): void {
const staleBackupWorkspaces: { workspacePath: string; backupPath: string; }[] = [];
const staleBackupWorkspaces: { workspaceIdentifier: string; backupPath: string; isEmptyWorkspace: boolean }[] = [];
const backupWorkspaces = backups.folderWorkspaces;
backupWorkspaces.forEach(workspacePath => {
const backupPath = this.toBackupPath(workspacePath);
this.sanitizeFolderWorkspaces(backups);
backups.folderWorkspaces.forEach(workspacePath => {
const backupPath = path.join(this.backupHome, this.getWorkspaceHash(workspacePath));
if (!this.hasBackupsSync(backupPath)) {
const backupWorkspace = this.sanitizePath(workspacePath);
staleBackupWorkspaces.push({ workspaceIdentifier: Uri.file(backupWorkspace).fsPath, backupPath, isEmptyWorkspace: false });
}
});
backups.emptyWorkspaces.forEach(backupFolder => {
const backupPath = path.join(this.backupHome, backupFolder);
if (!this.hasBackupsSync(backupPath)) {
staleBackupWorkspaces.push({ workspacePath, backupPath });
staleBackupWorkspaces.push({ workspaceIdentifier: backupFolder, backupPath, isEmptyWorkspace: true });
}
});
staleBackupWorkspaces.forEach(staleBackupWorkspace => {
const {backupPath, workspacePath} = staleBackupWorkspace;
const {backupPath, workspaceIdentifier, isEmptyWorkspace} = staleBackupWorkspace;
extfs.delSync(backupPath);
this.removeWorkspaceBackupPathSync(Uri.file(workspacePath));
this.removeBackupPathSync(workspaceIdentifier, isEmptyWorkspace);
});
}
......@@ -135,10 +183,11 @@ export class BackupMainService implements IBackupMainService {
}
}
protected toBackupPath(workspacePath: string): string {
const caseAwarePath = platform.isWindows || platform.isMacintosh ? workspacePath.toLowerCase() : workspacePath;
const workspaceHash = crypto.createHash('md5').update(caseAwarePath).digest('hex');
private sanitizePath(p) {
return platform.isLinux ? p : p.toLowerCase();
}
return path.join(this.backupHome, workspaceHash);
protected getWorkspaceHash(workspacePath: string): string {
return crypto.createHash('md5').update(this.sanitizePath(workspacePath)).digest('hex');
}
}
......@@ -28,16 +28,24 @@ class TestBackupMainService extends BackupMainService {
this.loadSync();
}
public removeWorkspaceBackupPathSync(workspace: Uri): void {
return super.removeWorkspaceBackupPathSync(workspace);
public removeBackupPathSync(workspaceIdenfitier: string, isEmptyWorkspace: boolean): void {
return super.removeBackupPathSync(workspaceIdenfitier, isEmptyWorkspace);
}
public loadSync(): void {
super.loadSync();
}
public sanitizeFolderWorkspaces(backups: IBackupWorkspacesFormat): void {
super.sanitizeFolderWorkspaces(backups);
}
public toBackupPath(workspacePath: string): string {
return super.toBackupPath(workspacePath);
return path.join(this.backupHome, super.getWorkspaceHash(workspacePath));
}
public getWorkspaceHash(workspacePath: string): string {
return super.getWorkspaceHash(workspacePath);
}
}
......@@ -66,84 +74,20 @@ suite('BackupMainService', () => {
extfs.del(backupHome, os.tmpdir(), done);
});
test('getWorkspaceBackupPaths should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
});
test('getWorkspaceBackupPaths should return [] when workspaces.json is not properly formed JSON', () => {
fs.writeFileSync(backupWorkspacesPath, '');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{]');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
});
test('getWorkspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is absent', () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
});
test('getWorkspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{}}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": ["bar"]}}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": []}}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": "bar"}}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":"foo"}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":1}');
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
});
test('pushWorkspaceBackupPathsSync should persist paths to workspaces.json', () => {
service.pushWorkspaceBackupPathsSync([fooFile, barFile]);
assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath, barFile.fsPath]);
});
test('removeWorkspaceBackupPath should remove workspaces from workspaces.json', done => {
service.pushWorkspaceBackupPathsSync([fooFile, barFile]);
service.removeWorkspaceBackupPathSync(fooFile);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [barFile.fsPath]);
service.removeWorkspaceBackupPathSync(barFile);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.folderWorkspaces, []);
done();
});
});
});
test('removeWorkspaceBackupPath should fail gracefully when removing a path that doesn\'t exist', done => {
const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath] };
pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.removeWorkspaceBackupPathSync(barFile);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath]);
done();
});
});
});
test('service validates backup workspaces on startup and cleans up', done => {
// 1) backup workspace path does not exist
service.pushWorkspaceBackupPathsSync([fooFile, barFile]);
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, false, null, barFile.fsPath);
service.loadSync();
assert.equal(service.getWorkspaceBackupPaths().length, 0);
assert.deepEqual(service.workspaceBackupPaths, []);
// 2) backup workspace path exists with empty contents within
fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
fs.mkdirSync(service.toBackupPath(barFile.fsPath));
service.pushWorkspaceBackupPathsSync([fooFile, barFile]);
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, false, null, barFile.fsPath);
service.loadSync();
assert.equal(service.getWorkspaceBackupPaths().length, 0);
assert.deepEqual(service.workspaceBackupPaths, []);
assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));
......@@ -152,27 +96,230 @@ suite('BackupMainService', () => {
fs.mkdirSync(service.toBackupPath(barFile.fsPath));
fs.mkdirSync(path.join(service.toBackupPath(fooFile.fsPath), 'file'));
fs.mkdirSync(path.join(service.toBackupPath(barFile.fsPath), 'untitled'));
service.pushWorkspaceBackupPathsSync([fooFile, barFile]);
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, false, null, barFile.fsPath);
service.loadSync();
assert.equal(service.getWorkspaceBackupPaths().length, 0);
assert.deepEqual(service.workspaceBackupPaths, []);
assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));
done();
});
test('toBackupPath ignores case on Windows and Mac', () => {
// Skip test on Linux
if (platform.isLinux) {
return;
}
suite('loadSync', () => {
test('workspaceBackupPaths should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.workspaceBackupPaths, []);
});
if (platform.isMacintosh) {
assert.equal(service.toBackupPath('/foo'), service.toBackupPath('/FOO'));
}
test('workspaceBackupPaths should return [] when workspaces.json is not properly formed JSON', () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
});
test('workspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is absent', () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
});
if (platform.isWindows) {
assert.equal(service.toBackupPath('c:\\foo'), service.toBackupPath('C:\\FOO'));
}
test('workspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{}}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": []}}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":{"foo": "bar"}}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":"foo"}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"folderWorkspaces":1}');
service.loadSync();
assert.deepEqual(service.workspaceBackupPaths, []);
});
test('emptyWorkspaceBackupPaths should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
});
test('emptyWorkspaceBackupPaths should return [] when workspaces.json is not properly formed JSON', () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
});
test('emptyWorkspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is absent', () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
});
test('emptyWorkspaceBackupPaths should return [] when folderWorkspaces in workspaces.json is not a string array', () => {
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{}}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": []}}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":{"foo": "bar"}}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":"foo"}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
fs.writeFileSync(backupWorkspacesPath, '{"emptyWorkspaces":1}');
service.loadSync();
assert.deepEqual(service.emptyWorkspaceBackupPaths, []);
});
});
suite('sanitizeFolderWorkspaces', () => {
test('should merge same cased paths on Windows and Mac', () => {
// Skip test on Linux
if (platform.isLinux) {
return;
}
const backups: IBackupWorkspacesFormat = {
folderWorkspaces: platform.isWindows ? ['c:\\foo', 'C:\\FOO', 'c:\\FOO'] : ['/foo', '/FOO'],
emptyWorkspaces: []
};
service.sanitizeFolderWorkspaces(backups);
if (platform.isWindows) {
assert.deepEqual(backups.folderWorkspaces, ['c:\\foo']);
} else {
assert.deepEqual(backups.folderWorkspaces, ['/foo']);
}
});
});
suite('registerWindowForBackups', () => {
test('pushWorkspaceBackupPathsSync should persist paths to workspaces.json', () => {
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, false, null, barFile.fsPath);
assert.deepEqual(service.workspaceBackupPaths, [fooFile.fsPath, barFile.fsPath]);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath, barFile.fsPath]);
});
});
});
suite('removeBackupPathSync', () => {
test('should remove folder workspaces from workspaces.json', done => {
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, false, null, barFile.fsPath);
service.removeBackupPathSync(fooFile.fsPath, false);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.folderWorkspaces, [barFile.fsPath]);
service.removeBackupPathSync(barFile.fsPath, false);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.folderWorkspaces, []);
done();
});
});
});
test('should remove empty workspaces from workspaces.json', done => {
service.registerWindowForBackups(1, true, 'foo');
service.registerWindowForBackups(2, true, 'bar');
service.removeBackupPathSync('foo', true);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.emptyWorkspaces, ['bar']);
service.removeBackupPathSync('bar', true);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.emptyWorkspaces, []);
done();
});
});
});
test('should fail gracefully when removing a path that doesn\'t exist', done => {
const workspacesJson: IBackupWorkspacesFormat = { folderWorkspaces: [fooFile.fsPath], emptyWorkspaces: [] };
pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)).then(() => {
service.removeBackupPathSync(barFile.fsPath, false);
service.removeBackupPathSync('test', true);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json.folderWorkspaces, [fooFile.fsPath]);
done();
});
});
});
});
suite('getWorkspaceHash', () => {
test('should perform an md5 hash on the path', () => {
assert.equal(service.getWorkspaceHash('/foo'), '1effb2475fcfba4f9e8b8a1dbc8f3caf');
});
test('should ignore case on Windows and Mac', () => {
// Skip test on Linux
if (platform.isLinux) {
return;
}
if (platform.isMacintosh) {
assert.equal(service.getWorkspaceHash('/foo'), service.getWorkspaceHash('/FOO'));
}
if (platform.isWindows) {
assert.equal(service.getWorkspaceHash('c:\\foo'), service.getWorkspaceHash('C:\\FOO'));
}
});
});
suite('getBackupPath', () => {
test('should return the window\'s correct path', done => {
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(2, true, 'test');
service.getBackupPath(1).then(window1Path => {
assert.equal(window1Path, service.toBackupPath(fooFile.fsPath));
service.getBackupPath(2).then(window2Path => {
assert.equal(window2Path, path.join(backupHome, 'test'));
done();
});
});
});
test('should override stale window paths with new paths', done => {
service.registerWindowForBackups(1, false, null, fooFile.fsPath);
service.registerWindowForBackups(1, false, null, barFile.fsPath);
service.getBackupPath(1).then(windowPath => {
assert.equal(windowPath, service.toBackupPath(barFile.fsPath));
done();
});
});
test('should throw when the window is not registered', () => {
assert.throws(() => service.getBackupPath(1));
});
});
});
\ No newline at end of file
......@@ -18,7 +18,7 @@ import { StorageService, InMemoryLocalStorage } from 'vs/workbench/services/stor
import { IEditorGroup, ConfirmResult } from 'vs/workbench/common/editor';
import Event, { Emitter } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { IBackupService, IBackupFileService, IBackupResult } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService, IBackupFileService, IBackupResult } from 'vs/workbench/services/backup/common/backup';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService';
......@@ -116,7 +116,7 @@ export class TestTextFileService extends TextFileService {
@IFileService fileService: IFileService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IInstantiationService instantiationService: IInstantiationService,
@IBackupService backupService: IBackupService,
@IBackupModelService backupService: IBackupModelService,
@IMessageService messageService: IMessageService
) {
super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, fileService, untitledEditorService, instantiationService, backupService, messageService);
......@@ -181,7 +181,7 @@ export function workbenchInstantiationService(): IInstantiationService {
instantiationService.stub(IModelService, createMockModelService(instantiationService));
instantiationService.stub(IFileService, TestFileService);
instantiationService.stub(IBackupFileService, new TestBackupFileService());
instantiationService.stub(IBackupService, new TestBackupService());
instantiationService.stub(IBackupModelService, new TestBackupService());
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(IMessageService, new TestMessageService());
instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService));
......@@ -623,7 +623,7 @@ export const TestFileService = {
}
};
export class TestBackupService implements IBackupService {
export class TestBackupService implements IBackupModelService {
public _serviceBrand: any;
public isHotExitEnabled: boolean = false;
......
......@@ -48,7 +48,6 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
}
export function startup(configuration: IWindowConfiguration): TPromise<void> {
// Ensure others can listen to zoom level changes
browser.setZoomFactor(webFrame.getZoomFactor());
browser.setZoomLevel(webFrame.getZoomLevel());
......
......@@ -87,6 +87,8 @@ import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc';
import { IUpdateService } from 'vs/platform/update/common/update';
import { URLChannelClient } from 'vs/platform/url/common/urlIpc';
import { IURLService } from 'vs/platform/url/common/url';
import { IBackupService } from 'vs/platform/backup/common/backup';
import { BackupChannelClient } from 'vs/platform/backup/common/backupIpc';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { ExtensionHostProcessWorker } from 'vs/workbench/electron-browser/extensionHost';
import { remote } from 'electron';
......@@ -439,6 +441,10 @@ export class WorkbenchShell {
const urlChannelClient = new URLChannelClient(urlChannel, windowIPCService.getWindowId());
serviceCollection.set(IURLService, urlChannelClient);
const backupChannel = mainProcessClient.getChannel('backup');
const backupChannelClient = new BackupChannelClient(backupChannel);
serviceCollection.set(IBackupService, backupChannelClient);
return [instantiationServiceImpl, serviceCollection];
}
......
......@@ -19,9 +19,9 @@ import assert = require('vs/base/common/assert');
import timer = require('vs/base/common/timer');
import { StopWatch } from 'vs/base/common/stopwatch';
import errors = require('vs/base/common/errors');
import { BackupService } from 'vs/workbench/services/backup/node/backupService';
import { BackupModelService } from 'vs/workbench/services/backup/node/backupModelService';
import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService';
import { IBackupService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Registry } from 'vs/platform/platform';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
......@@ -458,10 +458,10 @@ export class Workbench implements IPartService {
// Backup File Service
const workspace = this.contextService.getWorkspace();
serviceCollection.set(IBackupFileService, this.instantiationService.createInstance(BackupFileService, workspace ? workspace.resource : null));
serviceCollection.set(IBackupFileService, this.instantiationService.createInstance(BackupFileService, this.windowService.getCurrentWindowId()));
// Backup Service
serviceCollection.set(IBackupService, this.instantiationService.createInstance(BackupService));
serviceCollection.set(IBackupModelService, this.instantiationService.createInstance(BackupModelService));
// Text File Service
serviceCollection.set(ITextFileService, this.instantiationService.createInstance(TextFileService));
......
......@@ -7,7 +7,7 @@
import Uri from 'vs/base/common/uri';
import errors = require('vs/base/common/errors');
import { IBackupService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITextFileService, TextFileModelChangeEvent, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
......@@ -24,7 +24,7 @@ export class BackupModelTracker implements IWorkbenchContribution {
constructor(
@IBackupFileService private backupFileService: IBackupFileService,
@IBackupService private backupService: IBackupService,
@IBackupModelService private backupService: IBackupModelService,
@IFileService private fileService: IFileService,
@ITextFileService private textFileService: ITextFileService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
......
......@@ -12,7 +12,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import errors = require('vs/base/common/errors');
import { IBackupService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService, IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
......@@ -26,7 +26,7 @@ export class BackupRestorer implements IWorkbenchContribution {
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IPartService private partService: IPartService,
@IBackupService private backupService: IBackupService,
@IBackupModelService private backupService: IBackupModelService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IBackupFileService private backupFileService: IBackupFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
......
......@@ -12,7 +12,7 @@ import { ITextFileEditorModelManager, IRawTextContent } from 'vs/workbench/servi
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 IBackupModelService = createDecorator<IBackupModelService>('backupService');
export const IBackupFileService = createDecorator<IBackupFileService>('backupFileService');
export const BACKUP_FILE_RESOLVE_OPTIONS: IResolveContentOptions = { acceptTextOnly: true, encoding: 'utf-8' };
......@@ -26,7 +26,7 @@ export interface IBackupResult {
* A service that handles the lifecycle of backups, eg. listening for file changes and acting
* appropriately on shutdown.
*/
export interface IBackupService {
export interface IBackupModelService {
_serviceBrand: any;
isHotExitEnabled: boolean;
......
......@@ -11,6 +11,7 @@ import pfs = require('vs/base/node/pfs');
import * as platform from 'vs/base/common/platform';
import Uri from 'vs/base/common/uri';
import { IBackupFileService, BACKUP_FILE_UPDATE_OPTIONS } from 'vs/workbench/services/backup/common/backup';
import { IBackupService } from 'vs/platform/backup/common/backup';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService } from 'vs/platform/files/common/files';
import { TPromise } from 'vs/base/common/winjs.base';
......@@ -86,39 +87,34 @@ export class BackupFileService implements IBackupFileService {
private static readonly META_MARKER = '\n';
protected backupHome: string;
protected workspacesJsonPath: string;
private backupWorkspacePath: string;
private ready: TPromise<IBackupFilesModel>;
constructor(
private currentWorkspace: Uri,
windowId: number,
@IEnvironmentService private environmentService: IEnvironmentService,
@IFileService private fileService: IFileService
@IFileService private fileService: IFileService,
@IBackupService private backupService: IBackupService
) {
this.backupHome = environmentService.backupHome;
this.workspacesJsonPath = environmentService.backupWorkspacesPath;
if (this.currentWorkspace) {
this.backupWorkspacePath = path.join(this.backupHome, this.hashPath(this.currentWorkspace));
}
this.ready = this.init();
this.ready = this.init(windowId);
}
private get backupEnabled(): boolean {
return this.currentWorkspace && !this.environmentService.isExtensionDevelopment; // Hot exit is disabled for empty workspaces and when doing extension development
// Hot exit is disabled when doing extension development
return !this.environmentService.isExtensionDevelopment;
}
private init(): TPromise<IBackupFilesModel> {
private init(windowId: number): TPromise<IBackupFilesModel> {
const model = new BackupFilesModel();
if (!this.backupEnabled) {
return TPromise.as(model);
}
return model.resolve(this.backupWorkspacePath);
return this.backupService.getBackupPath(windowId).then(backupPath => {
this.backupWorkspacePath = backupPath;
return model.resolve(this.backupWorkspacePath);
});
}
public hasBackup(resource: Uri): TPromise<boolean> {
......
......@@ -7,7 +7,7 @@
import platform = require('vs/base/common/platform');
import Uri from 'vs/base/common/uri';
import { IBackupService, IBackupFileService, IBackupResult } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService, IBackupFileService, IBackupResult } from 'vs/workbench/services/backup/common/backup';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITextFileEditorModel, ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles';
......@@ -19,7 +19,7 @@ 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 {
export class BackupModelService implements IBackupModelService {
public _serviceBrand: any;
......@@ -49,8 +49,7 @@ export class BackupService implements IBackupService {
}
private onConfigurationChange(configuration: IFilesConfiguration): void {
// Hot exit is disabled for empty workspaces
this.configuredHotExit = this.contextService.getWorkspace() && configuration && configuration.files && configuration.files.hotExit;
this.configuredHotExit = configuration && configuration.files && configuration.files.hotExit;
}
/**
......@@ -91,9 +90,7 @@ export class BackupService implements IBackupService {
}
public get isHotExitEnabled(): boolean {
// If hot exit is enabled then save the dirty files in the workspace and then exit
// Hot exit is currently disabled for empty workspaces (#13733).
return !this.environmentService.isExtensionDevelopment && this.configuredHotExit && !!this.contextService.getWorkspace();
return !this.environmentService.isExtensionDevelopment && this.configuredHotExit;
}
public backupBeforeShutdown(dirtyToBackup: Uri[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): TPromise<IBackupResult> {
......@@ -141,11 +138,6 @@ export class BackupService implements IBackupService {
return TPromise.as(void 0);
}
const workspace = this.contextService.getWorkspace();
if (!workspace) {
return TPromise.as(void 0); // no backups to cleanup
}
return this.backupFileService.discardAllWorkspaceBackups();
}
......
......@@ -17,9 +17,11 @@ import Uri from 'vs/base/common/uri';
import { BackupFileService, BackupFilesModel } from 'vs/workbench/services/backup/node/backupFileService';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IBackupService } from 'vs/platform/backup/common/backup';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles';
import { TPromise } from 'vs/base/common/winjs.base';
class TestEnvironmentService extends EnvironmentService {
......@@ -32,19 +34,6 @@ class TestEnvironmentService extends EnvironmentService {
get backupWorkspacesPath(): string { return this._backupWorkspacesPath; }
}
class TestBackupFileService extends BackupFileService {
constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) {
const fileService = new FileService(workspace.fsPath, { disableWatcher: true }, null);
const testEnvironmentService = new TestEnvironmentService(backupHome, workspacesJsonPath);
super(workspace, testEnvironmentService, fileService);
}
public getBackupResource(resource: Uri): Uri {
return super.getBackupResource(resource);
}
}
const parentDir = path.join(os.tmpdir(), 'vsctests', 'service');
const backupHome = path.join(parentDir, 'Backups');
const workspacesJsonPath = path.join(backupHome, 'workspaces.json');
......@@ -58,6 +47,23 @@ const fooBackupPath = path.join(workspaceBackupPath, 'file', crypto.createHash('
const barBackupPath = path.join(workspaceBackupPath, 'file', crypto.createHash('md5').update(barFile.fsPath).digest('hex'));
const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', crypto.createHash('md5').update(platform.isLinux ? untitledFile.fsPath : untitledFile.fsPath.toLowerCase()).digest('hex'));
class TestBackupFileService extends BackupFileService {
constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) {
const fileService = new FileService(workspace.fsPath, { disableWatcher: true }, null);
const environmentService = new TestEnvironmentService(backupHome, workspacesJsonPath);
const backupService: IBackupService = {
_serviceBrand: null,
getBackupPath: () => TPromise.as(workspaceBackupPath)
};
super(1, environmentService, fileService, backupService);
}
public getBackupResource(resource: Uri): Uri {
return super.getBackupResource(resource);
}
}
suite('BackupFileService', () => {
let service: TestBackupFileService;
......@@ -78,159 +84,173 @@ suite('BackupFileService', () => {
extfs.del(backupHome, os.tmpdir(), done);
});
test('getBackupResource should get the correct backup path for text files', () => {
// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
const backupResource = fooFile;
const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex');
const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex');
const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath;
assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath);
});
test('getBackupResource should get the correct backup path for untitled files', () => {
// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePath>
const backupResource = Uri.from({ scheme: 'untitled', path: 'Untitled-1' });
const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex');
const filePathHash = crypto.createHash('md5').update(platform.isLinux ? backupResource.fsPath : backupResource.fsPath.toLowerCase()).digest('hex');
const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath;
assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath);
});
suite('getBackupResource', () => {
test('should get the correct backup path for text files', () => {
// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePathHash>
const backupResource = fooFile;
const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex');
const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex');
const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath;
assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath);
});
test('getBackupResource should ignore case on Windows and Mac', () => {
// Skip test on Linux
if (platform.isLinux) {
return;
}
test('should get the correct backup path for untitled files', () => {
// Format should be: <backupHome>/<workspaceHash>/<scheme>/<filePath>
const backupResource = Uri.from({ scheme: 'untitled', path: 'Untitled-1' });
const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex');
const filePathHash = crypto.createHash('md5').update(platform.isLinux ? backupResource.fsPath : backupResource.fsPath.toLowerCase()).digest('hex');
const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath;
assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath);
});
if (platform.isMacintosh) {
assert.equal(service.getBackupResource(Uri.file('/foo')).fsPath, service.getBackupResource(Uri.file('/FOO')).fsPath);
}
test('should ignore case on Windows and Mac', () => {
// Skip test on Linux
if (platform.isLinux) {
return;
}
if (platform.isWindows) {
assert.equal(service.getBackupResource(Uri.file('c:\\foo')).fsPath, service.getBackupResource(Uri.file('C:\\FOO')).fsPath);
}
});
if (platform.isMacintosh) {
assert.equal(service.getBackupResource(Uri.file('/foo')).fsPath, service.getBackupResource(Uri.file('/FOO')).fsPath);
}
test('doesTextFileHaveBackup should return whether a backup resource exists', done => {
pfs.mkdirp(path.dirname(fooBackupPath)).then(() => {
fs.writeFileSync(fooBackupPath, 'foo');
service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath);
service.hasBackup(fooFile).then(exists2 => {
assert.equal(exists2, true);
done();
});
if (platform.isWindows) {
assert.equal(service.getBackupResource(Uri.file('c:\\foo')).fsPath, service.getBackupResource(Uri.file('C:\\FOO')).fsPath);
}
});
});
test('backupResource - text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
done();
});
});
test('backupResource - untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
done();
suite('hasBackup', () => {
test('should return whether a backup resource exists', done => {
pfs.mkdirp(path.dirname(fooBackupPath)).then(() => {
fs.writeFileSync(fooBackupPath, 'foo');
service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath);
service.hasBackup(fooFile).then(exists2 => {
assert.equal(exists2, true);
done();
});
});
});
});
test('discardResourceBackup - text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
service.discardResourceBackup(fooFile).then(() => {
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
suite('backupResource', () => {
test('text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
assert.equal(fs.existsSync(fooBackupPath), true);
assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`);
done();
});
});
});
test('discardResourceBackup - untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
service.discardResourceBackup(untitledFile).then(() => {
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
test('untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
assert.equal(fs.existsSync(untitledBackupPath), true);
assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`);
done();
});
});
});
test('discardAllWorkspaceBackups - text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
service.backupResource(barFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
service.discardAllWorkspaceBackups().then(() => {
suite('discardResourceBackup', () => {
test('text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
service.discardResourceBackup(fooFile).then(() => {
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.existsSync(barBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0);
done();
});
});
});
});
test('discardAllWorkspaceBackups - untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
service.discardAllWorkspaceBackups().then(() => {
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
done();
test('untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
service.discardResourceBackup(untitledFile).then(() => {
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0);
done();
});
});
});
});
test('getWorkspaceFileBackups("file") - text file', done => {
service.backupResource(fooFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
service.backupResource(barFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
suite('discardAllWorkspaceBackups', () => {
test('text file', function (done: () => void) {
service.backupResource(fooFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1);
service.backupResource(barFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2);
service.discardAllWorkspaceBackups().then(() => {
assert.equal(fs.existsSync(fooBackupPath), false);
assert.equal(fs.existsSync(barBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false);
done();
});
});
});
});
});
test('getWorkspaceFileBackups("file") - untitled file', done => {
service.backupResource(untitledFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
done();
test('untitled file', function (done: () => void) {
service.backupResource(untitledFile, 'test').then(() => {
assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1);
service.discardAllWorkspaceBackups().then(() => {
assert.equal(fs.existsSync(untitledBackupPath), false);
assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false);
done();
});
});
});
});
test('getWorkspaceFileBackups("untitled") - untitled file', done => {
service.backupResource(untitledFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
done();
suite('getWorkspaceFileBackups', () => {
test('("file") - text file', done => {
service.backupResource(fooFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]);
service.backupResource(barFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]);
done();
});
});
});
});
});
test('("file") - untitled file', done => {
service.backupResource(untitledFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]);
done();
});
});
});
test('("untitled") - untitled file', done => {
service.backupResource(untitledFile, `test`).then(() => {
service.getWorkspaceFileBackups().then(textFiles => {
assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']);
done();
});
});
});
});
test('parseBackupContent', () => {
const rawTextContent: IRawTextContent = {
resource: null,
name: null,
mtime: null,
etag: null,
encoding: null,
value: TextModel.toRawText('metadata\ncontent', TextModel.DEFAULT_CREATION_OPTIONS),
valueLogicalHash: null
};
assert.equal(service.parseBackupContent(rawTextContent), 'content');
test('should separate metadata from content', () => {
const rawTextContent: IRawTextContent = {
resource: null,
name: null,
mtime: null,
etag: null,
encoding: null,
value: TextModel.toRawText('metadata\ncontent', TextModel.DEFAULT_CREATION_OPTIONS),
valueLogicalHash: null
};
assert.equal(service.parseBackupContent(rawTextContent), 'content');
});
});
});
......
......@@ -35,7 +35,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IBackupService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService } from 'vs/workbench/services/backup/common/backup';
import { IMessageService } from 'vs/platform/message/common/message';
class SettingsTestEnvironmentService extends EnvironmentService {
......@@ -60,7 +60,7 @@ class TestDirtyTextFileService extends TestTextFileService {
@IFileService fileService: IFileService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IInstantiationService instantiationService: IInstantiationService,
@IBackupService backupService: IBackupService,
@IBackupModelService backupService: IBackupModelService,
@IMessageService messageService: IMessageService
) {
super(lifecycleService, contextService, configurationService, telemetryService, editorService, editorGroupService, fileService, untitledEditorService, instantiationService, backupService, messageService);
......
......@@ -25,7 +25,7 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBackupService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService } from 'vs/workbench/services/backup/common/backup';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
/**
......@@ -57,7 +57,7 @@ export abstract class TextFileService implements ITextFileService {
@IFileService protected fileService: IFileService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IInstantiationService private instantiationService: IInstantiationService,
@IBackupService private backupService: IBackupService,
@IBackupModelService private backupService: IBackupModelService,
@IMessageService private messageService: IMessageService
) {
this.toUnbind = [];
......
......@@ -28,7 +28,7 @@ import { ModelBuilder } from 'vs/editor/node/model/modelBuilder';
import product from 'vs/platform/product';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBackupService } from 'vs/workbench/services/backup/common/backup';
import { IBackupModelService } from 'vs/workbench/services/backup/common/backup';
import { IMessageService } from 'vs/platform/message/common/message';
export class TextFileService extends AbstractTextFileService {
......@@ -48,7 +48,7 @@ export class TextFileService extends AbstractTextFileService {
@IWindowIPCService private windowService: IWindowIPCService,
@IModelService private modelService: IModelService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IBackupService backupService: IBackupService,
@IBackupModelService backupService: IBackupModelService,
@IMessageService messageService: IMessageService
) {
super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, fileService, untitledEditorService, instantiationService, backupService, messageService);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册