mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
229 lines
8.5 KiB
TypeScript
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)
|
|
};
|
|
}
|