multi root - stable workspace.id

This commit is contained in:
Benjamin Pasero
2017-06-15 15:29:10 +02:00
parent a248e33622
commit 73dad2ebb9
10 changed files with 78 additions and 80 deletions
@@ -95,7 +95,7 @@ suite('Workbench StorageSevice', () => {
assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo');
assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2');
ws = new Workspace(TestWorkspace.resource, TestWorkspace.name);
ws = new Workspace(TestWorkspace.resource, Date.now());
ws.uid = new Date().getTime() + 100;
s = new StorageService(storageImpl, null, ws);
+12 -7
View File
@@ -60,7 +60,6 @@ export interface IWorkspaceContextService {
* Given a workspace relative path, returns the resource with the absolute path.
*/
toResource: (workspaceRelativePath: string) => URI;
}
export interface IWorkspace {
@@ -71,6 +70,11 @@ export interface IWorkspace {
*/
resource: URI;
/**
* the creation date of the workspace if known.
*/
ctime: number;
/**
* the name of the workspace
*/
@@ -93,12 +97,13 @@ export interface IWorkspace2 {
* Mutliple roots in this workspace. First entry is master and never changes.
*/
readonly roots: URI[];
}
export class Workspace implements IWorkspace {
private _name: string;
constructor(private _resource: URI, private _name?: string) {
constructor(private _resource: URI, private _ctime?: number) {
this._name = paths.basename(this._resource.fsPath) || this._resource.fsPath;
}
public get resource(): URI {
@@ -109,6 +114,10 @@ export class Workspace implements IWorkspace {
return this._name;
}
public get ctime(): number {
return this._ctime;
}
public isInsideWorkspace(resource: URI): boolean {
if (resource) {
return isEqualOrParent(resource.fsPath, this._resource.fsPath, !isLinux /* ignorecase */);
@@ -132,8 +141,4 @@ export class Workspace implements IWorkspace {
return null;
}
public toJSON() {
return { resource: this._resource, name: this._name };
}
}
@@ -8,5 +8,5 @@ import URI from 'vs/base/common/uri';
export const TestWorkspace = new Workspace(
URI.file('C:\\testWorkspace'),
'Test Workspace'
Date.now()
);
@@ -58,6 +58,7 @@ export interface IEnvironment {
export interface IWorkspaceData {
id: string;
name: string;
roots: URI[];
}
@@ -16,7 +16,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
import { MainContext, MainProcessExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData } from './extHost.protocol';
import { createHash } from 'crypto';
const hasOwnProperty = Object.hasOwnProperty;
@@ -130,11 +129,7 @@ class ExtensionStoragePath {
return TPromise.as(undefined);
}
// TODO@joh what to do with multiple roots?
const storageName = createHash('md5')
.update(this._workspace.roots[0].fsPath)
.update(this._workspace.id || '')
.digest('hex');
const storageName = this._workspace.id;
const storagePath = join(this._environment.appSettingsHome, 'workspaceStorage', storageName);
return dirExists(storagePath).then(exists => {
+46 -54
View File
@@ -18,7 +18,7 @@ import paths = require('vs/base/common/paths');
import uri from 'vs/base/common/uri';
import strings = require('vs/base/common/strings');
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { IWorkspace, Workspace } from "vs/platform/workspace/common/workspace";
import { WorkspaceConfigurationService } from 'vs/workbench/services/configuration/node/configuration';
import { realpath, stat } from 'vs/base/node/pfs';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
@@ -95,47 +95,45 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] {
}
function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise<void> {
return getWorkspace(configuration).then(workspace => {
return resolveWorkspace(configuration).then(workspace => {
const environmentService = new EnvironmentService(configuration, configuration.execPath);
const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace);
const timerService = new TimerService((<any>window).MonacoEnvironment.timers as IInitData, !workspaceConfigurationService.hasWorkspace());
const storageService = createStorageService(workspace, configuration, environmentService);
return createStorageService(configuration, environmentService).then(storageService => {
// Since the configuration service is one of the core services that is used in so many places, we initialize it
// right before startup of the workbench shell to have its data ready for consumers
return workspaceConfigurationService.initialize().then(() => {
timerService.beforeDOMContentLoaded = Date.now();
// Since the configuration service is one of the core services that is used in so many places, we initialize it
// right before startup of the workbench shell to have its data ready for consumers
return workspaceConfigurationService.initialize().then(() => {
timerService.beforeDOMContentLoaded = Date.now();
return domContentLoaded().then(() => {
timerService.afterDOMContentLoaded = Date.now();
return domContentLoaded().then(() => {
timerService.afterDOMContentLoaded = Date.now();
// Open Shell
timerService.beforeWorkbenchOpen = Date.now();
const shell = new WorkbenchShell(document.body, {
contextService: workspaceConfigurationService,
configurationService: workspaceConfigurationService,
environmentService,
timerService,
storageService
}, configuration, options);
shell.open();
// Open Shell
timerService.beforeWorkbenchOpen = Date.now();
const shell = new WorkbenchShell(document.body, {
contextService: workspaceConfigurationService,
configurationService: workspaceConfigurationService,
environmentService,
timerService,
storageService
}, configuration, options);
shell.open();
// Inform user about loading issues from the loader
(<any>self).require.config({
onError: (err: any) => {
if (err.errorCode === 'load') {
shell.onUnexpectedError(loaderError(err));
}
// Inform user about loading issues from the loader
(<any>self).require.config({
onError: (err: any) => {
if (err.errorCode === 'load') {
shell.onUnexpectedError(loaderError(err));
}
});
}
});
});
});
});
}
function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
function resolveWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
if (!configuration.workspacePath) {
return TPromise.as(null);
}
@@ -153,10 +151,11 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace>
// update config
configuration.workspacePath = realWorkspacePath;
const workspaceResource = uri.file(realWorkspacePath);
const folderName = path.basename(realWorkspacePath) || realWorkspacePath;
return new Workspace(workspaceResource, folderName);
// resolve ctime of workspace
return stat(realWorkspacePath).then(folderStat => new Workspace(
uri.file(realWorkspacePath),
platform.isLinux ? folderStat.ino : folderStat.birthtime.getTime() // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
));
}, error => {
errors.onUnexpectedError(error);
@@ -164,31 +163,24 @@ function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace>
});
}
function createStorageService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): TPromise<IStorageService> {
let workspaceStatPromise: TPromise<fs.Stats> = TPromise.as(null);
if (configuration.workspacePath) {
workspaceStatPromise = stat(configuration.workspacePath);
function createStorageService(workspace: IWorkspace, configuration: IWindowConfiguration, environmentService: IEnvironmentService): IStorageService {
let id: IWorkspaceStorageIdentifier;
if (workspace) {
id = { resource: workspace.resource, uid: workspace.ctime };
} else if (configuration.backupPath) {
// if we do not have a workspace open, we need to find another identifier for the window to store
// workspace UI state. if we have a backup path in the configuration we can use that because this
// will be a unique identifier per window that is stable between restarts as long as there are
// dirty files in the workspace.
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
// scheme so that we can later distinguish these identifiers from the workspace one.
id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) };
}
return workspaceStatPromise.then(stat => {
let id: IWorkspaceStorageIdentifier;
if (stat) {
id = { resource: uri.file(configuration.workspacePath), uid: platform.isLinux ? stat.ino : stat.birthtime.getTime() };
} else if (configuration.backupPath) {
// if we do not have a workspace open, we need to find another identifier for the window to store
// workspace UI state. if we have a backup path in the configuration we can use that because this
// will be a unique identifier per window that is stable between restarts as long as there are
// dirty files in the workspace.
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
// scheme so that we can later distinguish these identifiers from the workspace one.
id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) };
}
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
return new StorageService(storage, storage, id);
});
return new StorageService(storage, storage, id);
}
function loaderError(err: Error): Error {
@@ -104,7 +104,6 @@ export class ExtensionHostMain {
return TPromise.as(null);
}
const desiredFilesMap: {
[filename: string]: boolean;
} = {};
@@ -107,7 +107,7 @@ suite('Workbench - TerminalInstance', () => {
});
test('should use to the workspace if it exists', () => {
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo') }), '/foo');
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/foo'), ctime: Date.now() }), '/foo');
});
test('should use an absolute custom cwd as is', () => {
@@ -117,11 +117,11 @@ suite('Workbench - TerminalInstance', () => {
test('should normalize a relative custom cwd against the workspace path', () => {
configHelper.config.cwd = 'foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo');
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo');
configHelper.config.cwd = './foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }), '/bar/foo');
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar/foo');
configHelper.config.cwd = '../foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar') }, ), '/foo');
assertPathsMatch(instance._getCwd({ executable: null, args: [] }, { resource: Uri.file('/bar'), ctime: Date.now() }, ), '/foo');
});
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
@@ -135,7 +135,7 @@ suite('Workbench - TerminalInstance', () => {
test('should ignore custom cwd when told to ignore', () => {
configHelper.config.cwd = '/foo';
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar') }), '/bar');
assertPathsMatch(instance._getCwd({ executable: null, args: [], ignoreConfigurationCwd: true }, { resource: Uri.file('/bar'), ctime: Date.now() }), '/bar');
});
});
});
@@ -69,7 +69,7 @@ class Workspace implements IWorkspace2 {
public get name(): string {
if (!this._name) {
this._name = this.roots.map(root => basename(root.fsPath)).join(', ');
this._name = this.roots.map(root => basename(root.fsPath) || root.fsPath).join(', ');
}
return this._name;
@@ -101,7 +101,13 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
constructor(private environmentService: IEnvironmentService, private singleRootWorkspace?: SingleRootWorkspace, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
super();
this.workspace = singleRootWorkspace ? new Workspace(createHash('md5').update(singleRootWorkspace.resource.toString()).digest('hex'), [singleRootWorkspace.resource]) : null; // TODO@Ben for now use the first root folder as id, but revisit this later
if (singleRootWorkspace) {
const workspaceId = createHash('md5').update(singleRootWorkspace.resource.fsPath).update(singleRootWorkspace.ctime ? String(singleRootWorkspace.ctime) : '').digest('hex');
this.workspace = new Workspace(workspaceId, [singleRootWorkspace.resource]);
} else {
this.workspace = null;
}
this.rootsTrieMap = new TrieMap<URI>(TrieMap.PathSplitter);
if (this.workspace) {
this.rootsTrieMap.insert(this.workspace.roots[0].fsPath, this.workspace.roots[0]);
@@ -14,7 +14,7 @@ suite('ExtHostWorkspace', function () {
test('asRelativePath', function () {
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')] });
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/Applications/NewsWoWBot')], name: 'Test' });
assert.equal(ws.getRelativePath('/Coding/Applications/NewsWoWBot/bernd/das/brot'), 'bernd/das/brot');
assert.equal(ws.getRelativePath('/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart'),
@@ -28,7 +28,7 @@ suite('ExtHostWorkspace', function () {
test('asRelativePath, same paths, #11402', function () {
const root = '/home/aeschli/workspaces/samples/docker';
const input = '/home/aeschli/workspaces/samples/docker';
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)] });
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file(root)], name: 'Test' });
assert.equal(ws.getRelativePath(input), input);
@@ -43,7 +43,7 @@ suite('ExtHostWorkspace', function () {
});
test('asRelativePath, multiple folders', function () {
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')] });
const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', roots: [URI.file('/Coding/One'), URI.file('/Coding/Two')], name: 'Test' });
assert.equal(ws.getRelativePath('/Coding/One/file.txt'), 'file.txt');
assert.equal(ws.getRelativePath('/Coding/Two/files/out.txt'), 'files/out.txt');
assert.equal(ws.getRelativePath('/Coding/Two2/files/out.txt'), '/Coding/Two2/files/out.txt');