提交 7a35ef4b 编写于 作者: B Benjamin Pasero

prefer workspace object over workspace config path

上级 c890422b
......@@ -24,6 +24,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow } from "vs/platform/windows/electron-main/windows";
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
export interface IWindowState {
width?: number;
......@@ -264,8 +265,8 @@ export class CodeWindow implements ICodeWindow {
return this.currentConfig ? this.currentConfig.backupPath : void 0;
}
public get openedWorkspaceConfigPath(): string {
return this.currentConfig ? this.currentConfig.workspaceConfigPath : void 0;
public get openedWorkspace(): IWorkspace {
return this.currentConfig ? this.currentConfig.workspace : void 0;
}
public get openedFolderPath(): string {
......
......@@ -29,7 +29,7 @@ import { IWindowsMainService, IOpenConfiguration } from "vs/platform/windows/ele
import { IHistoryMainService } from "vs/platform/history/electron-main/historyMainService";
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { TPromise } from "vs/base/common/winjs.base";
import { IWorkspacesMainService } from "vs/platform/workspaces/common/workspaces";
import { IWorkspacesMainService, IWorkspace } from "vs/platform/workspaces/common/workspaces";
enum WindowError {
UNRESPONSIVE,
......@@ -45,7 +45,7 @@ interface ILegacyWindowState extends IWindowState {
}
interface IWindowState {
workspaceConfigPath?: string;
workspace?: IWorkspace;
folderPath?: string;
backupPath: string;
uiState: ISingleWindowState;
......@@ -67,7 +67,7 @@ interface IOpenBrowserWindowOptions {
userEnv?: IProcessEnvironment;
cli?: ParsedArgs;
workspaceConfigPath?: string;
workspace?: IWorkspace;
folderPath?: string;
initialStartup?: boolean;
......@@ -84,8 +84,8 @@ interface IOpenBrowserWindowOptions {
interface IWindowToOpen extends IPath {
// the workspace config path for a Code instance to open
workspaceConfigPath?: string;
// the workspace for a Code instance to open
workspace?: IWorkspace;
// the folder path for a Code instance to open
folderPath?: string;
......@@ -261,9 +261,9 @@ export class WindowsManager implements IWindowsMainService {
}
// Any non extension host window with same workspace or folder
else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspaceConfigPath || !!win.openedFolderPath)) {
else if (!win.isExtensionDevelopmentHost && (!!win.openedWorkspace || !!win.openedFolderPath)) {
this.windowsState.openedWindows.forEach(o => {
const sameWorkspace = win.openedWorkspaceConfigPath && isEqual(o.workspaceConfigPath, win.openedWorkspaceConfigPath, !isLinux /* ignorecase */);
const sameWorkspace = win.openedWorkspace && o.workspace.id === win.openedWorkspace.id;
const sameFolder = win.openedFolderPath && isEqual(o.folderPath, win.openedFolderPath, !isLinux /* ignorecase */);
if (sameWorkspace || sameFolder) {
......@@ -283,7 +283,7 @@ export class WindowsManager implements IWindowsMainService {
private toWindowState(win: CodeWindow): IWindowState {
return {
workspaceConfigPath: win.openedWorkspaceConfigPath,
workspace: win.openedWorkspace,
folderPath: win.openedFolderPath,
backupPath: win.backupPath,
uiState: win.serializeWindowState()
......@@ -307,7 +307,7 @@ export class WindowsManager implements IWindowsMainService {
//
// These are windows to open to show workspaces
//
const workspacesToOpen = arrays.distinct(windowsToOpen.filter(win => !!win.workspaceConfigPath).map(win => win.workspaceConfigPath), workspaceConfigPath => isLinux ? workspaceConfigPath : workspaceConfigPath.toLowerCase()); // prevent duplicates
const workspacesToOpen = arrays.distinct(windowsToOpen.filter(win => !!win.workspace).map(win => win.workspace), workspace => workspace.id); // prevent duplicates
//
// These are windows to open to show either folders or files (including diffing files or creating them)
......@@ -319,15 +319,15 @@ export class WindowsManager implements IWindowsMainService {
//
const hotExitRestore = (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath);
const foldersToRestore = hotExitRestore ? this.backupService.getFolderBackupPaths() : [];
const workspacesToRestore = hotExitRestore ? this.backupService.getWorkspaceBackupPaths() : [];
const workspacesToRestore = hotExitRestore ? this.backupService.getWorkspaceBackups() : [];
let emptyToRestore = hotExitRestore ? this.backupService.getEmptyWindowBackupPaths() : [];
emptyToRestore.push(...windowsToOpen.filter(w => !w.workspaceConfigPath && !w.folderPath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
emptyToRestore.push(...windowsToOpen.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => path.basename(w.backupPath))); // add empty windows with backupPath
emptyToRestore = arrays.distinct(emptyToRestore); // prevent duplicates
//
// These are empty windows to open
//
const emptyToOpen = windowsToOpen.filter(win => !win.workspaceConfigPath && !win.folderPath && !win.filePath && !win.backupPath).length;
const emptyToOpen = windowsToOpen.filter(win => !win.workspace && !win.folderPath && !win.filePath && !win.backupPath).length;
// Open based on config
const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, filesToOpen, filesToCreate, filesToDiff);
......@@ -373,8 +373,8 @@ export class WindowsManager implements IWindowsMainService {
private doOpen(
openConfig: IOpenConfiguration,
workspacesToOpen: string[],
workspacesToRestore: string[],
workspacesToOpen: IWorkspace[],
workspacesToRestore: IWorkspace[],
foldersToOpen: string[],
foldersToRestore: string[],
emptyToRestore: string[],
......@@ -433,7 +433,7 @@ export class WindowsManager implements IWindowsMainService {
}
// Handle workspaces to open (instructed and to restore)
const allWorkspacesToOpen = arrays.distinct([...workspacesToOpen, ...workspacesToRestore], workspace => isLinux ? workspace : workspace.toLowerCase()); // prevent duplicates
const allWorkspacesToOpen = arrays.distinct([...workspacesToOpen, ...workspacesToRestore], workspace => workspace.id); // prevent duplicates
if (allWorkspacesToOpen.length > 0) {
// Check for existing instances
......@@ -454,12 +454,12 @@ export class WindowsManager implements IWindowsMainService {
// Open remaining ones
allWorkspacesToOpen.forEach(workspaceToOpen => {
if (windowsOnWorkspace.some(win => isEqual(win.openedWorkspaceConfigPath, workspaceToOpen, !isLinux /* ignorecase */))) {
if (windowsOnWorkspace.some(win => win.openedWorkspace.id === workspaceToOpen.id)) {
return; // ignore folders that are already open
}
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspaceConfigPath: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, filesToOpen, filesToCreate, filesToDiff));
// Reset these because we handled them
filesToOpen = [];
......@@ -564,7 +564,7 @@ export class WindowsManager implements IWindowsMainService {
userEnv: openConfig.userEnv,
cli: openConfig.cli,
initialStartup: openConfig.initialStartup,
workspaceConfigPath: folderOrWorkspace.workspaceConfigPath,
workspace: folderOrWorkspace.workspace,
folderPath: folderOrWorkspace.folderPath,
filesToOpen,
filesToCreate,
......@@ -658,12 +658,16 @@ export class WindowsManager implements IWindowsMainService {
case 'one':
if (lastActiveWindow) {
// return workspace/folder path if it is valid
const workspaceOrFolder = lastActiveWindow.workspaceConfigPath || lastActiveWindow.folderPath;
if (workspaceOrFolder) {
const validatedWorkspaceOrFolder = this.parsePath(workspaceOrFolder);
if (validatedWorkspaceOrFolder) {
return [validatedWorkspaceOrFolder];
// workspace
if (lastActiveWindow.workspace) {
return [{ workspace: lastActiveWindow.workspace }];
}
// folder (if path is valid)
else if (lastActiveWindow.folderPath) {
const validatedFolder = this.parsePath(lastActiveWindow.folderPath);
if (validatedFolder) {
return [validatedFolder];
}
}
......@@ -678,20 +682,26 @@ export class WindowsManager implements IWindowsMainService {
// folders: restore last opened folders only
case 'all':
case 'folders':
const windowsToOpen: IWindowToOpen[] = [];
// Windows with Workspaces/Folders
const lastOpenedWorkspacesOrFolders = this.windowsState.openedWindows.filter(w => !!w.workspaceConfigPath || !!w.folderPath).map(o => o.workspaceConfigPath || o.folderPath);
const lastActiveWorkspaceOrFolder = lastActiveWindow && (lastActiveWindow.workspaceConfigPath || lastActiveWindow.folderPath);
if (lastActiveWorkspaceOrFolder) {
lastOpenedWorkspacesOrFolders.push(lastActiveWorkspaceOrFolder);
// Workspaces
const workspaces = this.windowsState.openedWindows.filter(w => !!w.workspace).map(w => w.workspace);
if (lastActiveWindow && lastActiveWindow.workspace) {
workspaces.push(lastActiveWindow.workspace);
}
windowsToOpen.push(...workspaces.map(workspace => ({ workspace })));
const windowsToOpen = lastOpenedWorkspacesOrFolders.map(candidate => this.parsePath(candidate)).filter(path => !!path);
// Folders
const folders = this.windowsState.openedWindows.filter(w => !!w.folderPath).map(w => w.folderPath);
if (lastActiveWindow && lastActiveWindow.folderPath) {
folders.push(lastActiveWindow.folderPath);
}
windowsToOpen.push(...folders.map(candidate => this.parsePath(candidate)).filter(path => !!path));
// Windows that were Empty
if (restoreWindows === 'all') {
const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspaceConfigPath && !w.folderPath && w.backupPath).map(w => w.backupPath);
const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspaceConfigPath && !lastActiveWindow.folderPath && lastActiveWindow.backupPath;
const lastOpenedEmpty = this.windowsState.openedWindows.filter(w => !w.workspace && !w.folderPath && w.backupPath).map(w => w.backupPath);
const lastActiveEmpty = lastActiveWindow && !lastActiveWindow.workspace && !lastActiveWindow.folderPath && lastActiveWindow.backupPath;
if (lastActiveEmpty) {
lastOpenedEmpty.push(lastActiveEmpty);
}
......@@ -745,13 +755,17 @@ export class WindowsManager implements IWindowsMainService {
try {
const candidateStat = fs.statSync(candidate);
if (candidateStat) {
// File / Workspace
if (candidateStat.isFile()) {
const isWorkspaceConfig = this.workspacesService.isWorkspace(candidate);
// Workspace
const workspace = this.workspacesService.resolveWorkspaceSync(candidate);
if (workspace) {
return { workspace };
}
// File
return {
workspaceConfigPath: isWorkspaceConfig ? candidate : void 0,
filePath: !isWorkspaceConfig ? candidate : void 0,
filePath: candidate,
lineNumber: gotoLineMode ? parsedPath.line : void 0,
columnNumber: gotoLineMode ? parsedPath.column : void 0
};
......@@ -843,7 +857,7 @@ export class WindowsManager implements IWindowsMainService {
configuration.execPath = process.execPath;
configuration.userEnv = assign({}, this.initialUserEnv, options.userEnv || {});
configuration.isInitialStartup = options.initialStartup;
configuration.workspaceConfigPath = options.workspaceConfigPath;
configuration.workspace = options.workspace;
configuration.folderPath = options.folderPath;
configuration.filesToOpen = options.filesToOpen;
configuration.filesToCreate = options.filesToCreate;
......@@ -932,8 +946,8 @@ export class WindowsManager implements IWindowsMainService {
// Register window for backups
if (!configuration.extensionDevelopmentPath) {
if (configuration.workspaceConfigPath) {
configuration.backupPath = this.backupService.registerWorkspaceBackupSync(configuration.workspaceConfigPath);
if (configuration.workspace) {
configuration.backupPath = this.backupService.registerWorkspaceBackupSync(configuration.workspace);
} else if (configuration.folderPath) {
configuration.backupPath = this.backupService.registerFolderBackupSync(configuration.folderPath);
} else {
......@@ -957,8 +971,8 @@ export class WindowsManager implements IWindowsMainService {
}
// Known Workspace - load from stored settings
if (configuration.workspaceConfigPath) {
const stateForWorkspace = this.windowsState.openedWindows.filter(o => isEqual(o.workspaceConfigPath, configuration.workspaceConfigPath, !isLinux /* ignorecase */)).map(o => o.uiState);
if (configuration.workspace) {
const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === configuration.workspace.id).map(o => o.uiState);
if (stateForWorkspace.length) {
return stateForWorkspace[0];
}
......
......@@ -10,9 +10,10 @@ import * as fs from 'fs';
import * as platform from 'vs/base/common/platform';
import * as paths from 'vs/base/common/paths';
import { OpenContext } from 'vs/platform/windows/common/windows';
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
export interface ISimpleWindow {
openedWorkspaceConfigPath?: string;
openedWorkspace?: IWorkspace;
openedFolderPath?: string;
openedFilePath?: string;
extensionDevelopmentPath?: string;
......@@ -126,12 +127,12 @@ export function findWindowOnFolder<W extends ISimpleWindow>(windows: W[], folder
return null;
}
export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], workspaceConfigPath: string): W {
export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], workspace: IWorkspace): W {
if (windows.length) {
const res = windows.filter(w => {
// match on workspace
if (typeof w.openedWorkspaceConfigPath === 'string' && (paths.isEqual(w.openedWorkspaceConfigPath, workspaceConfigPath, !platform.isLinux /* ignorecase */))) {
if (w.openedWorkspace && w.openedWorkspace.id === workspace.id) {
return true;
}
......
......@@ -4,9 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
export interface IBackupWorkspacesFormat {
rootWorkspaces: string[];
rootWorkspaces: IWorkspace[];
folderWorkspaces: string[];
emptyWorkspaces: string[];
}
......@@ -16,11 +17,11 @@ export const IBackupMainService = createDecorator<IBackupMainService>('backupMai
export interface IBackupMainService {
_serviceBrand: any;
getWorkspaceBackupPaths(): string[];
getWorkspaceBackups(): IWorkspace[];
getFolderBackupPaths(): string[];
getEmptyWindowBackupPaths(): string[];
registerWorkspaceBackupSync(workspaceConfigPath: string): string;
registerWorkspaceBackupSync(workspace: IWorkspace): string;
registerFolderBackupSync(folderPath: string): string;
registerEmptyWindowBackupSync(backupFolder?: string): string;
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
import { ILogService } from "vs/platform/log/common/log";
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
export class BackupMainService implements IBackupMainService {
......@@ -35,7 +36,7 @@ export class BackupMainService implements IBackupMainService {
this.loadSync();
}
public getWorkspaceBackupPaths(): string[] {
public getWorkspaceBackups(): IWorkspace[] {
if (this.isHotExitOnExitAndWindowClose()) {
// Only non-folder windows are restored on main process launch when
// hot exit is configured as onExitAndWindowClose.
......@@ -63,16 +64,16 @@ export class BackupMainService implements IBackupMainService {
return this.backups.emptyWorkspaces.slice(0); // return a copy
}
public registerWorkspaceBackupSync(workspaceConfigPath: string): string {
this.pushBackupPathsSync(workspaceConfigPath, this.backups.rootWorkspaces);
public registerWorkspaceBackupSync(workspace: IWorkspace): string {
this.pushBackupPathsSync(workspace, this.backups.rootWorkspaces);
return path.join(this.backupHome, this.getWorkspaceHash(workspaceConfigPath));
return path.join(this.backupHome, workspace.id);
}
public registerFolderBackupSync(folderPath: string): string {
this.pushBackupPathsSync(folderPath, this.backups.folderWorkspaces);
return path.join(this.backupHome, this.getWorkspaceHash(folderPath));
return path.join(this.backupHome, this.getFolderHash(folderPath));
}
public registerEmptyWindowBackupSync(backupFolder?: string): string {
......@@ -87,16 +88,14 @@ export class BackupMainService implements IBackupMainService {
return path.join(this.backupHome, backupFolder);
}
private pushBackupPathsSync(workspaceIdentifier: string, target: string[]): string {
private pushBackupPathsSync(workspaceIdentifier: string | IWorkspace, target: (string | IWorkspace)[]): void {
if (this.indexOf(workspaceIdentifier, target) === -1) {
target.push(workspaceIdentifier);
this.saveSync();
}
return workspaceIdentifier;
}
protected removeBackupPathSync(workspaceIdentifier: string, target: string[]): void {
protected removeBackupPathSync(workspaceIdentifier: string | IWorkspace, target: (string | IWorkspace)[]): void {
if (!target) {
return;
}
......@@ -108,14 +107,22 @@ export class BackupMainService implements IBackupMainService {
this.saveSync();
}
private indexOf(workspaceIdentifier: string, target: string[]): number {
private indexOf(workspaceIdentifier: string | IWorkspace, target: (string | IWorkspace)[]): number {
if (!target) {
return -1;
}
const sanitizedWorkspaceIdentifier = this.sanitizePath(workspaceIdentifier);
const sanitizedWorkspaceIdentifier = this.sanitizeId(workspaceIdentifier);
return arrays.firstIndex(target, id => this.sanitizeId(id) === sanitizedWorkspaceIdentifier);
}
private sanitizeId(workspaceIdentifier: string | IWorkspace): string {
if (typeof workspaceIdentifier === 'string') {
return this.sanitizePath(workspaceIdentifier);
}
return arrays.firstIndex(target, id => this.sanitizePath(id) === sanitizedWorkspaceIdentifier);
return workspaceIdentifier.id;
}
protected loadSync(): void {
......@@ -126,10 +133,10 @@ export class BackupMainService implements IBackupMainService {
backups = Object.create(null);
}
// Ensure rootWorkspaces is a string[]
// Ensure rootWorkspaces is a object[]
if (backups.rootWorkspaces) {
const rws = backups.rootWorkspaces;
if (!Array.isArray(rws) || rws.some(r => typeof r !== 'string')) {
if (!Array.isArray(rws) || rws.some(r => typeof r !== 'object')) {
backups.rootWorkspaces = [];
}
} else {
......@@ -167,31 +174,34 @@ export class BackupMainService implements IBackupMainService {
// De-duplicate folder/workspace backups. don't worry about cleaning them up any duplicates as
// they will be removed when there are no backups.
backups.folderWorkspaces = arrays.distinct(backups.folderWorkspaces, ws => this.sanitizePath(ws));
backups.rootWorkspaces = arrays.distinct(backups.rootWorkspaces, ws => this.sanitizePath(ws));
backups.rootWorkspaces = arrays.distinct(backups.rootWorkspaces, ws => this.sanitizePath(ws.id));
return backups;
}
private validateBackupWorkspaces(backups: IBackupWorkspacesFormat): void {
const staleBackupWorkspaces: { workspaceIdentifier: string; backupPath: string; target: string[] }[] = [];
const staleBackupWorkspaces: { workspaceIdentifier: string | IWorkspace; backupPath: string; target: (string | IWorkspace)[] }[] = [];
const workspaceAndFolders: { path: string, target: string[] }[] = [];
workspaceAndFolders.push(...backups.rootWorkspaces.map(r => ({ path: r, target: backups.rootWorkspaces })));
workspaceAndFolders.push(...backups.folderWorkspaces.map(f => ({ path: f, target: backups.folderWorkspaces })));
const workspaceAndFolders: { workspaceIdentifier: string | IWorkspace, target: (string | IWorkspace)[] }[] = [];
workspaceAndFolders.push(...backups.rootWorkspaces.map(r => ({ workspaceIdentifier: r, target: backups.rootWorkspaces })));
workspaceAndFolders.push(...backups.folderWorkspaces.map(f => ({ workspaceIdentifier: f, target: backups.folderWorkspaces })));
// Validate Workspace and Folder Backups
workspaceAndFolders.forEach(workspaceOrFolder => {
const backupPath = path.join(this.backupHome, this.getWorkspaceHash(workspaceOrFolder.path));
const workspaceId = workspaceOrFolder.workspaceIdentifier;
const workspacePath = typeof workspaceId === 'string' ? workspaceId : workspaceId.configPath;
const backupPath = path.join(this.backupHome, typeof workspaceId === 'string' ? this.getFolderHash(workspaceId) : workspaceId.id);
const hasBackups = this.hasBackupsSync(backupPath);
const missingWorkspace = hasBackups && !fs.existsSync(workspaceOrFolder.path);
const missingWorkspace = hasBackups && !fs.existsSync(workspacePath);
// If the workspace/folder has no backups, make sure to delete it
// If the workspace/folder has backups, but the target workspace is missing, convert backups to empty ones
if (!hasBackups || missingWorkspace) {
staleBackupWorkspaces.push({ workspaceIdentifier: workspaceOrFolder.path, backupPath, target: workspaceOrFolder.target });
staleBackupWorkspaces.push({ workspaceIdentifier: workspaceId, backupPath, target: workspaceOrFolder.target });
if (missingWorkspace) {
const identifier = this.pushBackupPathsSync(this.getRandomEmptyWindowId(), this.backups.emptyWorkspaces);
const identifier = this.getRandomEmptyWindowId();
this.pushBackupPathsSync(identifier, this.backups.emptyWorkspaces);
const newEmptyWindowBackupPath = path.join(path.dirname(backupPath), identifier);
try {
fs.renameSync(backupPath, newEmptyWindowBackupPath);
......@@ -265,7 +275,7 @@ export class BackupMainService implements IBackupMainService {
return platform.isLinux ? p : p.toLowerCase();
}
protected getWorkspaceHash(workspacePath: string): string {
return crypto.createHash('md5').update(this.sanitizePath(workspacePath)).digest('hex');
protected getFolderHash(folderPath: string): string {
return crypto.createHash('md5').update(this.sanitizePath(folderPath)).digest('hex');
}
}
......@@ -20,6 +20,8 @@ import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup';
import { HotExitConfiguration } from 'vs/platform/files/common/files';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { LogMainService } from "vs/platform/log/common/log";
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
import { createHash } from "crypto";
class TestBackupMainService extends BackupMainService {
......@@ -37,8 +39,8 @@ class TestBackupMainService extends BackupMainService {
return this.backups;
}
public removeBackupPathSync(workspaceIdenfitier: string, target: string[]): void {
return super.removeBackupPathSync(workspaceIdenfitier, target);
public removeBackupPathSync(workspaceIdentifier: string | IWorkspace, target: (string | IWorkspace)[]): void {
return super.removeBackupPathSync(workspaceIdentifier, target);
}
public loadSync(): void {
......@@ -50,14 +52,26 @@ class TestBackupMainService extends BackupMainService {
}
public toBackupPath(workspacePath: string): string {
return path.join(this.backupHome, super.getWorkspaceHash(workspacePath));
return path.join(this.backupHome, super.getFolderHash(workspacePath));
}
public getWorkspaceHash(workspacePath: string): string {
return super.getWorkspaceHash(workspacePath);
public getFolderHash(folderPath: string): string {
return super.getFolderHash(folderPath);
}
}
function toWorkspace(path: string): IWorkspace {
return {
id: createHash('md5').update(sanitizePath(path)).digest('hex'),
configPath: path,
folders: []
};
}
function sanitizePath(p: string): string {
return platform.isLinux ? p : p.toLowerCase();
}
suite('BackupMainService', () => {
const parentDir = path.join(os.tmpdir(), 'vsctests', 'service');
const backupHome = path.join(parentDir, 'Backups');
......@@ -135,18 +149,18 @@ suite('BackupMainService', () => {
test('service validates backup workspaces on startup and cleans up (root workspaces)', done => {
// 1) backup workspace path does not exist
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(barFile.fsPath);
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
// 2) backup workspace path exists with empty contents within
fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
fs.mkdirSync(service.toBackupPath(barFile.fsPath));
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(barFile.fsPath);
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));
......@@ -155,10 +169,10 @@ 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.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(barFile.fsPath);
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath));
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
assert.ok(!fs.exists(service.toBackupPath(fooFile.fsPath)));
assert.ok(!fs.exists(service.toBackupPath(barFile.fsPath)));
......@@ -168,12 +182,12 @@ suite('BackupMainService', () => {
fs.mkdirSync(service.toBackupPath(fooFile.fsPath));
fs.mkdirSync(service.toBackupPath(barFile.fsPath));
fs.mkdirSync(fileBackups);
service.registerWorkspaceBackupSync(fooFile.fsPath);
assert.equal(service.getWorkspaceBackupPaths().length, 1);
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
assert.equal(service.getWorkspaceBackups().length, 1);
assert.equal(service.getEmptyWindowBackupPaths().length, 0);
fs.writeFileSync(path.join(fileBackups, 'backup.txt'), '');
service.loadSync();
assert.equal(service.getWorkspaceBackupPaths().length, 0);
assert.equal(service.getWorkspaceBackups().length, 0);
assert.equal(service.getEmptyWindowBackupPaths().length, 1);
done();
......@@ -231,55 +245,56 @@ suite('BackupMainService', () => {
assert.deepEqual(service.getFolderBackupPaths(), []);
});
test('getWorkspaceBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
test('getWorkspaceBackups() should return [] when workspaces.json doesn\'t exist', () => {
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackupPaths() should return [] when workspaces.json is not properly formed JSON', () => {
test('getWorkspaceBackups() should return [] when workspaces.json is not properly formed JSON', () => {
fs.writeFileSync(backupWorkspacesPath, '');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{]');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, 'foo');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackupPaths() should return [] when folderWorkspaces in workspaces.json is absent', () => {
test('getWorkspaceBackups() should return [] when folderWorkspaces in workspaces.json is absent', () => {
fs.writeFileSync(backupWorkspacesPath, '{}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackupPaths() should return [] when rootWorkspaces in workspaces.json is not a string array', () => {
test('getWorkspaceBackups() should return [] when rootWorkspaces in workspaces.json is not a object array', () => {
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{}}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": ["bar"]}}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": []}}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":{"foo": "bar"}}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":"foo"}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
fs.writeFileSync(backupWorkspacesPath, '{"rootWorkspaces":1}');
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getWorkspaceBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', () => {
service.registerWorkspaceBackupSync(fooFile.fsPath.toUpperCase());
assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath.toUpperCase()]);
test('getWorkspaceBackups() should return [] when files.hotExit = "onExitAndWindowClose"', () => {
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
assert.equal(service.getWorkspaceBackups().length, 1);
assert.deepEqual(service.getWorkspaceBackups().map(r => r.configPath), [fooFile.fsPath.toUpperCase()]);
configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE);
service.loadSync();
assert.deepEqual(service.getWorkspaceBackupPaths(), []);
assert.deepEqual(service.getWorkspaceBackups(), []);
});
test('getEmptyWorkspaceBackupPaths() should return [] when workspaces.json doesn\'t exist', () => {
......@@ -356,7 +371,7 @@ suite('BackupMainService', () => {
}
const backups: IBackupWorkspacesFormat = {
rootWorkspaces: platform.isWindows ? ['c:\\FOO', 'C:\\FOO', 'c:\\foo'] : ['/FOO', '/foo'],
rootWorkspaces: platform.isWindows ? [toWorkspace('c:\\FOO'), toWorkspace('C:\\FOO'), toWorkspace('c:\\foo')] : [toWorkspace('/FOO'), toWorkspace('/foo')],
folderWorkspaces: [],
emptyWorkspaces: []
};
......@@ -365,9 +380,9 @@ suite('BackupMainService', () => {
assert.equal(backups.rootWorkspaces.length, 1);
if (platform.isWindows) {
assert.deepEqual(backups.rootWorkspaces, ['c:\\FOO'], 'should return the first duplicated entry');
assert.deepEqual(backups.rootWorkspaces.map(r => r.configPath), ['c:\\FOO'], 'should return the first duplicated entry');
} else {
assert.deepEqual(backups.rootWorkspaces, ['/FOO'], 'should return the first duplicated entry');
assert.deepEqual(backups.rootWorkspaces.map(r => r.configPath), ['/FOO'], 'should return the first duplicated entry');
}
});
});
......@@ -385,12 +400,22 @@ suite('BackupMainService', () => {
});
test('should persist paths to workspaces.json (root workspace)', done => {
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(barFile.fsPath);
assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath, barFile.fsPath]);
const ws1 = toWorkspace(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspace(barFile.fsPath);
service.registerWorkspaceBackupSync(ws2);
assert.deepEqual(service.getWorkspaceBackups().map(b => b.configPath), [fooFile.fsPath, barFile.fsPath]);
assert.equal(ws1.id, service.getWorkspaceBackups()[0].id);
assert.equal(ws2.id, service.getWorkspaceBackups()[1].id);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces, [fooFile.fsPath, barFile.fsPath]);
assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath, barFile.fsPath]);
assert.equal(ws1.id, json.rootWorkspaces[0].id);
assert.equal(ws2.id, json.rootWorkspaces[1].id);
done();
});
});
......@@ -406,11 +431,11 @@ suite('BackupMainService', () => {
});
test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', done => {
service.registerWorkspaceBackupSync(fooFile.fsPath.toUpperCase());
assert.deepEqual(service.getWorkspaceBackupPaths(), [fooFile.fsPath.toUpperCase()]);
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
assert.deepEqual(service.getWorkspaceBackups().map(b => b.configPath), [fooFile.fsPath.toUpperCase()]);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces, [fooFile.fsPath.toUpperCase()]);
assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath.toUpperCase()]);
done();
});
});
......@@ -434,13 +459,15 @@ suite('BackupMainService', () => {
});
test('should remove folder workspaces from workspaces.json (root workspace)', done => {
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(barFile.fsPath);
service.removeBackupPathSync(fooFile.fsPath, service.backupsData.rootWorkspaces);
const ws1 = toWorkspace(fooFile.fsPath);
service.registerWorkspaceBackupSync(ws1);
const ws2 = toWorkspace(barFile.fsPath);
service.registerWorkspaceBackupSync(ws2);
service.removeBackupPathSync(ws1, service.backupsData.rootWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => {
const json = <IBackupWorkspacesFormat>JSON.parse(buffer);
assert.deepEqual(json.rootWorkspaces, [barFile.fsPath]);
service.removeBackupPathSync(barFile.fsPath, service.backupsData.rootWorkspaces);
assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [barFile.fsPath]);
service.removeBackupPathSync(ws2, service.backupsData.rootWorkspaces);
pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => {
const json2 = <IBackupWorkspacesFormat>JSON.parse(content);
assert.deepEqual(json2.rootWorkspaces, []);
......@@ -481,7 +508,7 @@ suite('BackupMainService', () => {
suite('getWorkspaceHash', () => {
test('should perform an md5 hash on the path', () => {
assert.equal(service.getWorkspaceHash('/foo'), '1effb2475fcfba4f9e8b8a1dbc8f3caf');
assert.equal(service.getFolderHash('/foo'), '1effb2475fcfba4f9e8b8a1dbc8f3caf');
});
test('should ignore case on Windows and Mac', () => {
......@@ -491,11 +518,11 @@ suite('BackupMainService', () => {
}
if (platform.isMacintosh) {
assert.equal(service.getWorkspaceHash('/foo'), service.getWorkspaceHash('/FOO'));
assert.equal(service.getFolderHash('/foo'), service.getFolderHash('/FOO'));
}
if (platform.isWindows) {
assert.equal(service.getWorkspaceHash('c:\\foo'), service.getWorkspaceHash('C:\\FOO'));
assert.equal(service.getFolderHash('c:\\foo'), service.getFolderHash('C:\\FOO'));
}
});
});
......@@ -515,13 +542,13 @@ suite('BackupMainService', () => {
});
test('should handle case insensitive paths properly (registerWindowForBackupsSync) (root workspace)', done => {
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.registerWorkspaceBackupSync(fooFile.fsPath.toUpperCase());
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath));
service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase()));
if (platform.isLinux) {
assert.equal(service.getWorkspaceBackupPaths().length, 2);
assert.equal(service.getWorkspaceBackups().length, 2);
} else {
assert.equal(service.getWorkspaceBackupPaths().length, 1);
assert.equal(service.getWorkspaceBackups().length, 1);
}
done();
......@@ -546,25 +573,5 @@ suite('BackupMainService', () => {
done();
});
test('should handle case insensitive paths properly (removeBackupPathSync) (root workspace)', done => {
// same case
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.removeBackupPathSync(fooFile.fsPath, service.backupsData.rootWorkspaces);
assert.equal(service.getWorkspaceBackupPaths().length, 0);
// mixed case
service.registerWorkspaceBackupSync(fooFile.fsPath);
service.removeBackupPathSync(fooFile.fsPath.toUpperCase(), service.backupsData.rootWorkspaces);
if (platform.isLinux) {
assert.equal(service.getWorkspaceBackupPaths().length, 1);
} else {
assert.equal(service.getWorkspaceBackupPaths().length, 0);
}
done();
});
});
});
\ No newline at end of file
......@@ -207,7 +207,7 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest {
perfAppReady?: number;
perfWindowLoadTime?: number;
workspaceConfigPath?: string;
workspace?: IWorkspace;
folderPath?: string;
backupPath?: string;
......
......@@ -12,6 +12,7 @@ import Event from 'vs/base/common/event';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspace } from "vs/platform/workspaces/common/workspaces";
export interface ICodeWindow {
id: number;
......@@ -19,7 +20,7 @@ export interface ICodeWindow {
config: IWindowConfiguration;
openedFolderPath: string;
openedWorkspaceConfigPath: string;
openedWorkspace: IWorkspace;
lastFocusTime: number;
......
......@@ -327,7 +327,7 @@ export class WindowsService implements IWindowsService, IDisposable {
const codeWindow = this.windowsMainService.getWindowById(windowId);
if (codeWindow) {
this.windowsMainService.open({ context: OpenContext.API, cli: this.environmentService.args, pathsToOpen: [workspace.workspaceConfigPath], windowToUse: codeWindow });
this.windowsMainService.open({ context: OpenContext.API, cli: this.environmentService.args, pathsToOpen: [workspace.configPath], windowToUse: codeWindow });
}
return TPromise.as(null);
......
......@@ -12,7 +12,7 @@ export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('w
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
export interface IWorkspace extends IStoredWorkspace {
workspaceConfigPath: string;
configPath: string;
}
export interface IStoredWorkspace {
......@@ -23,7 +23,7 @@ export interface IStoredWorkspace {
export interface IWorkspacesMainService extends IWorkspacesService {
_serviceBrand: any;
isWorkspace(path: string): boolean;
resolveWorkspaceSync(path: string): IWorkspace;
}
export interface IWorkspacesService {
......
......@@ -11,6 +11,8 @@ import { isParent } from "vs/platform/files/common/files";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
import { extname, join } from "path";
import { mkdirp, writeFile } from "vs/base/node/pfs";
import { readFileSync } from "fs";
import { isLinux } from "vs/base/common/platform";
export class WorkspacesMainService implements IWorkspacesMainService {
......@@ -22,8 +24,26 @@ export class WorkspacesMainService implements IWorkspacesMainService {
this.workspacesHome = environmentService.workspacesHome;
}
public isWorkspace(path: string): boolean {
return isParent(path, this.environmentService.workspacesHome) || extname(path) === '.vscode';
public resolveWorkspaceSync(path: string): IWorkspace {
const isWorkspace = isParent(path, this.environmentService.workspacesHome, !isLinux /* ignore case */) || extname(path) === '.code';
if (!isWorkspace) {
return null; // does not look like a valid workspace config file
}
try {
const workspace = JSON.parse(readFileSync(path, 'utf8')) as IStoredWorkspace;
if (typeof workspace.id !== 'string' || !Array.isArray(workspace.folders)) {
return null; // looks like an invalid workspace file
}
return {
id: workspace.id,
folders: workspace.folders,
configPath: path
};
} catch (error) {
return null; // unable to read or parse as workspace file
}
}
public createWorkspace(folders: string[] = []): TPromise<IWorkspace> {
......@@ -40,7 +60,7 @@ export class WorkspacesMainService implements IWorkspacesMainService {
return writeFile(workspaceConfigPath, JSON.stringify(storedWorkspace, null, '\t')).then(() => ({
id: workspaceId,
folders,
workspaceConfigPath
configPath: workspaceConfigPath
}));
});
}
......
......@@ -48,10 +48,10 @@ suite('WorkspacesMainService', () => {
test('createWorkspace (no folders)', done => {
return service.createWorkspace().then(workspace => {
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.workspaceConfigPath));
assert.ok(fs.existsSync(workspace.configPath));
assert.equal(workspace.folders.length, 0);
const ws = JSON.parse(fs.readFileSync(workspace.workspaceConfigPath).toString()) as IStoredWorkspace;
const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace;
assert.equal(ws.id, workspace.id);
assert.deepEqual(ws.folders, workspace.folders);
......@@ -62,16 +62,34 @@ suite('WorkspacesMainService', () => {
test('createWorkspace (folders)', done => {
return service.createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => {
assert.ok(workspace);
assert.ok(fs.existsSync(workspace.workspaceConfigPath));
assert.ok(fs.existsSync(workspace.configPath));
assert.equal(workspace.folders.length, 2);
assert.equal(workspace.folders[0], process.cwd());
assert.equal(workspace.folders[1], os.tmpdir());
const ws = JSON.parse(fs.readFileSync(workspace.workspaceConfigPath).toString()) as IStoredWorkspace;
const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace;
assert.equal(ws.id, workspace.id);
assert.deepEqual(ws.folders, workspace.folders);
done();
});
});
test('resolveWorkspace', done => {
return service.createWorkspace().then(workspace => {
// is not resolved because config path is no in workspaces home
assert.ok(!service.resolveWorkspaceSync(workspace.configPath));
// make it a valid workspace path
const newPath = path.join(path.dirname(workspace.configPath), 'workspace.code');
fs.renameSync(workspace.configPath, newPath);
workspace.configPath = newPath;
const resolved = service.resolveWorkspaceSync(workspace.configPath);
assert.deepEqual(resolved, workspace);
done();
});
});
});
\ No newline at end of file
......@@ -169,7 +169,7 @@ interface IMultiRootWorkspaceData {
}
function resolveWorkspaceData(configuration: IWindowConfiguration): TPromise<ISingleFolderWorkspaceData | IMultiRootWorkspaceData> {
if (configuration.workspaceConfigPath) {
if (configuration.workspace) {
return resolveMultiRootWorkspaceData(configuration);
}
......@@ -209,7 +209,7 @@ function resolveSingleFolderWorkspaceData(configuration: IWindowConfiguration):
}
function resolveMultiRootWorkspaceData(configuration: IWindowConfiguration): TPromise<IMultiRootWorkspaceData> {
return readFile(configuration.workspaceConfigPath).then(buffer => {
return readFile(configuration.workspace.configPath).then(buffer => {
const contents = buffer.toString('utf8');
return JSON.parse(contents) as IMultiRootWorkspaceData;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册