Files
vscode/src/vs/platform/workspaces/electron-main/workspacesMainService.ts
T
2019-04-01 08:45:46 +02:00

229 lines
8.5 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { join, dirname } from 'vs/base/common/path';
import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs';
import { readFileSync, existsSync, mkdirSync } from 'fs';
import { isLinux } from 'vs/base/common/platform';
import { Event, Emitter } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { createHash } from 'crypto';
import * as json from 'vs/base/common/json';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { originalFSPath, dirname as resourcesDirname, isEqualOrParent, joinPath } from 'vs/base/common/resources';
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export class WorkspacesMainService extends Disposable implements IWorkspacesMainService {
_serviceBrand: any;
private readonly untitledWorkspacesHome: URI; // local URI that contains all untitled workspaces
private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter<IWorkspaceIdentifier>());
get onUntitledWorkspaceDeleted(): Event<IWorkspaceIdentifier> { return this._onUntitledWorkspaceDeleted.event; }
constructor(
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
this.untitledWorkspacesHome = environmentService.untitledWorkspacesHome;
}
resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null {
if (!this.isWorkspacePath(uri)) {
return null; // does not look like a valid workspace config file
}
if (uri.scheme !== Schemas.file) {
return null;
}
let contents: string;
try {
contents = readFileSync(uri.fsPath, 'utf8');
} catch (error) {
return null; // invalid workspace
}
return this.doResolveWorkspace(uri, contents);
}
private isWorkspacePath(uri: URI): boolean {
return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri.path);
}
private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null {
try {
const workspace = this.doParseStoredWorkspace(path, contents);
const workspaceIdentifier = getWorkspaceIdentifier(path);
return {
id: workspaceIdentifier.id,
configPath: workspaceIdentifier.configPath,
folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)),
remoteAuthority: workspace.remoteAuthority
};
} catch (error) {
this.logService.warn(error.toString());
}
return null;
}
private doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {
// Parse workspace file
let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser
// Filter out folders which do not have a path or uri set
if (Array.isArray(storedWorkspace.folders)) {
storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder));
}
// Validate
if (!Array.isArray(storedWorkspace.folders)) {
throw new Error(`${path.toString()} looks like an invalid workspace file.`);
}
return storedWorkspace;
}
private isInsideWorkspacesHome(path: URI): boolean {
return isEqualOrParent(path, this.environmentService.untitledWorkspacesHome);
}
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier> {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
return mkdirp(dirname(configPath)).then(() => {
return writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')).then(() => workspace);
});
}
createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier {
const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
const configPath = workspace.configPath.fsPath;
const configPathDir = dirname(configPath);
if (!existsSync(configPathDir)) {
const configPathDirDir = dirname(configPathDir);
if (!existsSync(configPathDirDir)) {
mkdirSync(configPathDirDir);
}
mkdirSync(configPathDir);
}
writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
return workspace;
}
private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } {
const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString();
const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId);
const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME);
const storedWorkspaceFolder: IStoredWorkspaceFolder[] = [];
for (const folder of folders) {
storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, untitledWorkspaceConfigFolder));
}
return {
workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath),
storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority }
};
}
getWorkspaceIdentifier(configPath: URI): Promise<IWorkspaceIdentifier> {
return Promise.resolve(getWorkspaceIdentifier(configPath));
}
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean {
return this.isInsideWorkspacesHome(workspace.configPath);
}
deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
if (!this.isUntitledWorkspace(workspace)) {
return; // only supported for untitled workspaces
}
// Delete from disk
this.doDeleteUntitledWorkspaceSync(workspace);
// Event
this._onUntitledWorkspaceDeleted.fire(workspace);
}
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
this.deleteUntitledWorkspaceSync(workspace);
return Promise.resolve();
}
private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
const configPath = originalFSPath(workspace.configPath);
try {
// Delete Workspace
rimrafSync(dirname(configPath));
// Mark Workspace Storage to be deleted
const workspaceStoragePath = join(this.environmentService.workspaceStorageHome, workspace.id);
if (existsSync(workspaceStoragePath)) {
writeFileSync(join(workspaceStoragePath, 'obsolete'), '');
}
} catch (error) {
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] {
let untitledWorkspaces: IUntitledWorkspaceInfo[] = [];
try {
const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
for (const untitledWorkspacePath of untitledWorkspacePaths) {
const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath);
if (!resolvedWorkspace) {
this.doDeleteUntitledWorkspaceSync(workspace);
} else {
untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
}
}
} catch (error) {
if (error && error.code !== 'ENOENT') {
this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
}
}
return untitledWorkspaces;
}
}
function getWorkspaceId(configPath: URI): string {
let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString();
if (!isLinux) {
workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system
}
return createHash('md5').update(workspaceConfigPath).digest('hex');
}
export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier {
return {
configPath,
id: getWorkspaceId(configPath)
};
}