提交 fd0c9390 编写于 作者: B Benjamin Pasero

storage - reduce stat calls when creating workspace

上级 c2ef06ef
......@@ -13,7 +13,7 @@ import * as comparer from 'vs/base/common/comparers';
import * as platform from 'vs/base/common/platform';
import { URI as uri } from 'vs/base/common/uri';
import { IWorkspaceContextService, Workspace, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService';
import { WorkspaceService, ISingleFolderWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload } from 'vs/workbench/services/configuration/node/configurationService';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { stat } from 'vs/base/node/pfs';
......@@ -44,6 +44,7 @@ import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc';
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
import { Schemas } from 'vs/base/common/network';
import { sanitizeFilePath } from 'vs/base/node/extfs';
import { basename } from 'path';
gracefulFs.gracefulify(fs); // enable gracefulFs
......@@ -139,23 +140,46 @@ function openWorkbench(configuration: IWindowConfiguration): Promise<void> {
}
function createAndInitializeWorkspaceService(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise<WorkspaceService> {
return validateFolderUri(configuration.folderUri, configuration.verbose).then(validatedFolderUri => {
let workspaceInitializationPayload: Promise<IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload> = Promise.resolve(void 0);
// Multi-root workspace
if (configuration.workspace) {
workspaceInitializationPayload = Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload);
}
// Single-folder workspace
else if (configuration.folderUri) {
workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri, configuration.verbose);
}
return workspaceInitializationPayload.then(payload => {
// Fallback to empty workspace
if (!payload) {
payload = { id: configuration.backupPath ? uri.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '' } as IEmptyWorkspaceInitializationPayload;
}
const workspaceService = new WorkspaceService(environmentService);
return workspaceService.initialize(configuration.workspace || validatedFolderUri || configuration).then(() => workspaceService, error => workspaceService);
return workspaceService.initialize(payload).then(() => workspaceService, error => workspaceService);
});
}
function validateFolderUri(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<uri> {
function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, verbose: boolean): Promise<ISingleFolderWorkspaceInitializationPayload> {
// Return early if we do not have a single folder uri or if it is a non file uri
if (!folderUri || folderUri.scheme !== Schemas.file) {
return Promise.resolve(folderUri);
// Return early the folder is not local
if (folderUri.scheme !== Schemas.file) {
return Promise.resolve({ folder: folderUri });
}
// Ensure absolute existing folder path
// For local: ensure path is absolute and exists
const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
return stat(sanitizedFolderPath).then(stat => uri.file(sanitizedFolderPath), error => {
return stat(sanitizedFolderPath).then(stat => {
return {
folder: uri.file(sanitizedFolderPath),
stat
} as ISingleFolderWorkspaceInitializationPayload;
}, error => {
if (verbose) {
errors.onUnexpectedError(error);
}
......
......@@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { dirname, basename } from 'path';
import { dirname } from 'path';
import { Stats } from 'fs';
import * as assert from 'vs/base/common/assert';
import { Event, Emitter } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
import { equals, deepClone } from 'vs/base/common/objects';
import { Disposable } from 'vs/base/common/lifecycle';
import { Queue } from 'vs/base/common/async';
import { stat, writeFile } from 'vs/base/node/pfs';
import { writeFile } from 'vs/base/node/pfs';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
......@@ -25,7 +26,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry';
import { createHash } from 'crypto';
import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import product from 'vs/platform/node/product';
......@@ -40,6 +40,14 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { isEqual } from 'vs/base/common/resources';
export type IMultiFolderWorkspaceInitializationPayload = IWorkspaceIdentifier;
export interface ISingleFolderWorkspaceInitializationPayload { folder: ISingleFolderWorkspaceIdentifier; stat?: Stats; }
export interface IEmptyWorkspaceInitializationPayload { id: string; }
function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload {
return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier));
}
export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService {
public _serviceBrand: any;
......@@ -293,7 +301,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
return this._configuration.keys();
}
initialize(arg: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWindowConfiguration, postInitialisationTask: () => void = () => null): Promise<any> {
initialize(arg: IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload, postInitialisationTask: () => void = () => null): Promise<any> {
return this.createWorkspace(arg)
.then(workspace => this.updateWorkspaceAndInitializeConfiguration(workspace, postInitialisationTask));
}
......@@ -320,12 +328,12 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
this.jsonEditingService = instantiationService.createInstance(JSONEditingService);
}
private createWorkspace(arg: IWorkspaceIdentifier | URI | IWindowConfiguration): Promise<Workspace> {
private createWorkspace(arg: IMultiFolderWorkspaceInitializationPayload | ISingleFolderWorkspaceInitializationPayload | IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
if (isWorkspaceIdentifier(arg)) {
return this.createMultiFolderWorkspace(arg);
}
if (isSingleFolderWorkspaceIdentifier(arg)) {
if (isSingleFolderWorkspaceInitializationPayload(arg)) {
return this.createSingleFolderWorkspace(arg);
}
......@@ -342,35 +350,33 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat
});
}
private createSingleFolderWorkspace(folder: URI): Promise<Workspace> {
if (folder.scheme === Schemas.file) {
return stat(folder.fsPath)
.then(workspaceStat => {
let ctime: number;
if (isLinux) {
ctime = workspaceStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = workspaceStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof workspaceStat.birthtimeMs === 'number') {
ctime = Math.floor(workspaceStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = workspaceStat.birthtime.getTime();
}
}
private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise<Workspace> {
const folder = singleFolder.folder;
if (folder.scheme === Schemas.file && singleFolder.stat) {
const workspaceStat = singleFolder.stat;
let ctime: number;
if (isLinux) {
ctime = workspaceStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
} else if (isMacintosh) {
ctime = workspaceStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
} else if (isWindows) {
if (typeof workspaceStat.birthtimeMs === 'number') {
ctime = Math.floor(workspaceStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
} else {
ctime = workspaceStat.birthtime.getTime();
}
}
const id = createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
return new Workspace(id, toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime);
});
const id = createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
return Promise.resolve(new Workspace(id, toWorkspaceFolders([{ path: folder.fsPath }]), null, ctime));
} else {
const id = createHash('md5').update(folder.toString()).digest('hex');
return Promise.resolve(new Workspace(id, toWorkspaceFolders([{ uri: folder.toString() }]), null));
}
}
private createEmptyWorkspace(configuration: IWindowConfiguration): Promise<Workspace> {
let id = configuration.backupPath ? URI.from({ path: basename(configuration.backupPath), scheme: 'empty' }).toString() : '';
return Promise.resolve(new Workspace(id));
private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise<Workspace> {
return Promise.resolve(new Workspace(emptyWorkspace.id));
}
private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisationTask: () => void): Promise<void> {
......
......@@ -30,7 +30,6 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { mkdirp } from 'vs/base/node/pfs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ICommandService } from 'vs/platform/commands/common/commands';
......@@ -102,7 +101,7 @@ suite('ConfigurationEditingService', () => {
instantiationService.stub(IEnvironmentService, environmentService);
const workspaceService = new WorkspaceService(environmentService);
instantiationService.stub(IWorkspaceContextService, workspaceService);
return workspaceService.initialize(noWorkspace ? {} as IWindowConfiguration : URI.file(workspaceDir)).then(() => {
return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir) }).then(() => {
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IFileService, new FileService(workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }));
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
......
......@@ -31,7 +31,6 @@ import { TextModelResolverService } from 'vs/workbench/services/textmodelResolve
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
class SettingsTestEnvironmentService extends EnvironmentService {
......@@ -84,7 +83,7 @@ suite('WorkspaceContextService - Folder', () => {
const globalSettingsFile = path.join(parentDir, 'settings.json');
const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile);
workspaceContextService = new WorkspaceService(environmentService);
return (<WorkspaceService>workspaceContextService).initialize(URI.file(folderDir));
return (<WorkspaceService>workspaceContextService).initialize({ folder: URI.file(folderDir) });
});
});
......@@ -408,7 +407,7 @@ suite('WorkspaceService - Initialization', () => {
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IEnvironmentService, environmentService);
return workspaceService.initialize(<IWindowConfiguration>{}).then(() => {
return workspaceService.initialize({ id: '' }).then(() => {
const fileService = new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true });
instantiationService.stub(IFileService, fileService);
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
......@@ -442,7 +441,7 @@ suite('WorkspaceService - Initialization', () => {
testObject.onDidChangeWorkspaceFolders(target);
testObject.onDidChangeConfiguration(target);
return testObject.initialize(URI.file(path.join(parentResource, '1')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) })
.then(() => {
assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue');
assert.equal(target.callCount, 3);
......@@ -471,7 +470,7 @@ suite('WorkspaceService - Initialization', () => {
fs.writeFileSync(path.join(parentResource, '1', '.vscode', 'settings.json'), '{ "initialization.testSetting1": "workspaceValue" }');
return testObject.initialize(URI.file(path.join(parentResource, '1')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) })
.then(() => {
assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue');
assert.equal(target.callCount, 4);
......@@ -545,7 +544,7 @@ suite('WorkspaceService - Initialization', () => {
test('initialize a folder workspace from a folder workspace with no configuration changes', () => {
return testObject.initialize(URI.file(path.join(parentResource, '1')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) })
.then(() => {
fs.writeFileSync(globalSettingsFile, '{ "initialization.testSetting1": "userValue" }');
......@@ -557,7 +556,7 @@ suite('WorkspaceService - Initialization', () => {
testObject.onDidChangeWorkspaceFolders(target);
testObject.onDidChangeConfiguration(target);
return testObject.initialize(URI.file(path.join(parentResource, '2')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '2')) })
.then(() => {
assert.equal(testObject.getValue('initialization.testSetting1'), 'userValue');
assert.equal(target.callCount, 1);
......@@ -573,7 +572,7 @@ suite('WorkspaceService - Initialization', () => {
test('initialize a folder workspace from a folder workspace with configuration changes', () => {
return testObject.initialize(URI.file(path.join(parentResource, '1')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '1')) })
.then(() => {
const target = sinon.spy();
......@@ -583,7 +582,7 @@ suite('WorkspaceService - Initialization', () => {
testObject.onDidChangeConfiguration(target);
fs.writeFileSync(path.join(parentResource, '2', '.vscode', 'settings.json'), '{ "initialization.testSetting1": "workspaceValue2" }');
return testObject.initialize(URI.file(path.join(parentResource, '2')))
return testObject.initialize({ folder: URI.file(path.join(parentResource, '2')) })
.then(() => {
assert.equal(testObject.getValue('initialization.testSetting1'), 'workspaceValue2');
assert.equal(target.callCount, 2);
......@@ -598,7 +597,7 @@ suite('WorkspaceService - Initialization', () => {
test('initialize a multi folder workspace from a folder workspacce triggers change events in the right order', () => {
const folderDir = path.join(parentResource, '1');
return testObject.initialize(URI.file(folderDir))
return testObject.initialize({ folder: URI.file(folderDir) })
.then(() => {
const target = sinon.spy();
......@@ -663,7 +662,7 @@ suite('WorkspaceConfigurationService - Folder', () => {
instantiationService.stub(IConfigurationService, workspaceService);
instantiationService.stub(IEnvironmentService, environmentService);
return workspaceService.initialize(URI.file(folderDir)).then(() => {
return workspaceService.initialize({ folder: URI.file(folderDir) }).then(() => {
const fileService = new FileService(<IWorkspaceContextService>workspaceService, TestEnvironmentService, new TestTextResourceConfigurationService(), workspaceService, new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true });
instantiationService.stub(IFileService, fileService);
instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册