Skip to content

Commit 3bf3f69

Browse files
author
Benjamin Pasero
committed
multiroot - get rid of workspace.uid (only used for local storage)
1 parent 555900b commit 3bf3f69

9 files changed

Lines changed: 101 additions & 96 deletions

File tree

src/vs/editor/browser/standalone/simpleServices.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { ResolvedKeybinding, Keybinding, createKeybinding, SimpleKeybinding } fr
3737
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
3838
import { OS } from 'vs/base/common/platform';
3939
import { IRange } from 'vs/editor/common/core/range';
40+
import { generateUuid } from "vs/base/common/uuid";
4041

4142
export class SimpleEditor implements IEditor {
4243

@@ -499,9 +500,11 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService {
499500
public readonly onDidChangeWorkspaceRoots: Event<URI[]> = this._onDidChangeWorkspaceRoots.event;
500501

501502
private readonly folders: URI[];
503+
private readonly id: string;
502504

503505
constructor(private workspace?: Workspace) {
504506
this.folders = workspace ? [workspace.resource] : [];
507+
this.id = generateUuid();
505508
}
506509

507510
public getFolders(): URI[] {
@@ -513,7 +516,7 @@ export class SimpleWorkspaceContextService implements IWorkspaceContextService {
513516
}
514517

515518
public getWorkspace2(): IWorkspace2 {
516-
return this.workspace ? { id: `${this.workspace.uid}`, roots: [this.workspace.resource] } : void 0;
519+
return this.workspace ? { id: `${this.id}`, roots: [this.workspace.resource] } : void 0;
517520
}
518521

519522
public hasWorkspace(): boolean {

src/vs/platform/storage/common/storageService.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export interface IStorage {
2323
export interface IWorkspaceStorageIdentifier {
2424
resource: URI;
2525
uid?: number;
26-
name?: string;
2726
}
2827

2928
export class StorageService implements IStorageService {
@@ -55,7 +54,7 @@ export class StorageService implements IStorageService {
5554
// Make sure to delete all workspace storage if the workspace has been recreated meanwhile
5655
// which is only possible if a UID property is provided that we can check on
5756
if (workspaceIdentifier && types.isNumber(workspaceIdentifier.uid)) {
58-
this.cleanupWorkspaceScope(workspaceIdentifier.uid, workspaceIdentifier.name);
57+
this.cleanupWorkspaceScope(workspaceIdentifier.uid);
5958
}
6059
}
6160

@@ -76,13 +75,13 @@ export class StorageService implements IStorageService {
7675
return `${strings.rtrim(workspaceIdStr, '/')}/`;
7776
}
7877

79-
private cleanupWorkspaceScope(workspaceId: number, workspaceName: string): void {
78+
private cleanupWorkspaceScope(workspaceUid: number): void {
8079

8180
// Get stored identifier from storage
8281
const id = this.getInteger(StorageService.WORKSPACE_IDENTIFIER, StorageScope.WORKSPACE);
8382

8483
// If identifier differs, assume the workspace got recreated and thus clean all storage for this workspace
85-
if (types.isNumber(id) && workspaceId !== id) {
84+
if (types.isNumber(id) && workspaceUid !== id) {
8685
const keyPrefix = this.toStorageKey('', StorageScope.WORKSPACE);
8786
const toDelete: string[] = [];
8887
const length = this.workspaceStorage.length;
@@ -99,19 +98,15 @@ export class StorageService implements IStorageService {
9998
}
10099
}
101100

102-
if (toDelete.length > 0) {
103-
console.warn('Clearing previous version of local storage for workspace ', workspaceName);
104-
}
105-
106101
// Run the delete
107102
toDelete.forEach((keyToDelete) => {
108103
this.workspaceStorage.removeItem(keyToDelete);
109104
});
110105
}
111106

112107
// Store workspace identifier now
113-
if (workspaceId !== id) {
114-
this.store(StorageService.WORKSPACE_IDENTIFIER, workspaceId, StorageScope.WORKSPACE);
108+
if (workspaceUid !== id) {
109+
this.store(StorageService.WORKSPACE_IDENTIFIER, workspaceUid, StorageScope.WORKSPACE);
115110
}
116111
}
117112

src/vs/platform/storage/test/storageService.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ suite('Workbench StorageSevice', () => {
7777

7878
test('StorageSevice cleans up when workspace changes', () => {
7979
let storageImpl = new InMemoryLocalStorage();
80-
let s = new StorageService(storageImpl, null, contextService.getWorkspace());
80+
let ws = contextService.getWorkspace();
81+
ws.uid = new Date().getTime();
82+
let s = new StorageService(storageImpl, null, ws);
8183

8284
s.store('key1', 'foobar');
8385
s.store('key2', 'something');
@@ -93,7 +95,8 @@ suite('Workbench StorageSevice', () => {
9395
assert.strictEqual(s.get('wkey1', StorageScope.WORKSPACE), 'foo');
9496
assert.strictEqual(s.get('wkey2', StorageScope.WORKSPACE), 'foo2');
9597

96-
let ws: any = new Workspace(TestWorkspace.resource, new Date().getTime() + 100, TestWorkspace.name);
98+
ws = new Workspace(TestWorkspace.resource, TestWorkspace.name);
99+
ws.uid = new Date().getTime() + 100;
97100
s = new StorageService(storageImpl, null, ws);
98101

99102
assert.strictEqual(s.get('key1', StorageScope.GLOBAL), 'foobar');

src/vs/platform/workspace/common/workspace.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,6 @@ export interface IWorkspace {
6565
*/
6666
resource: URI;
6767

68-
/**
69-
* the unique identifier of the workspace. if the workspace is deleted and recreated
70-
* the identifier also changes. this makes the uid more unique compared to the id which
71-
* is just derived from the workspace name.
72-
*/
73-
uid?: number;
74-
7568
/**
7669
* the name of the workspace
7770
*/
@@ -94,17 +87,13 @@ export interface IWorkspace2 {
9487

9588
export class Workspace implements IWorkspace {
9689

97-
constructor(private _resource: URI, private _uid?: number, private _name?: string) {
90+
constructor(private _resource: URI, private _name?: string) {
9891
}
9992

10093
public get resource(): URI {
10194
return this._resource;
10295
}
10396

104-
public get uid(): number {
105-
return this._uid;
106-
}
107-
10897
public get name(): string {
10998
return this._name;
11099
}
@@ -134,6 +123,6 @@ export class Workspace implements IWorkspace {
134123
}
135124

136125
public toJSON() {
137-
return { resource: this._resource, uid: this._uid, name: this._name };
126+
return { resource: this._resource, name: this._name };
138127
}
139128
}

src/vs/platform/workspace/test/common/testWorkspace.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ import URI from 'vs/base/common/uri';
88

99
export const TestWorkspace = new Workspace(
1010
URI.file('C:\\testWorkspace'),
11-
Date.now(),
1211
'Test Workspace'
1312
);

src/vs/workbench/electron-browser/main.ts

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import { IInitData } from 'vs/workbench/services/timer/common/timerService';
2828
import { TimerService } from 'vs/workbench/services/timer/node/timerService';
2929
import { KeyboardMapperFactory } from "vs/workbench/services/keybinding/electron-browser/keybindingService";
3030
import { IWindowConfiguration, IPath } from "vs/platform/windows/common/windows";
31+
import { IStorageService } from "vs/platform/storage/common/storage";
32+
import { IEnvironmentService } from "vs/platform/environment/common/environment";
33+
import { StorageService, inMemoryLocalStorageInstance, IWorkspaceStorageIdentifier } from "vs/platform/storage/common/storageService";
3134

3235
import { webFrame } from 'electron';
3336

@@ -62,12 +65,8 @@ export function startup(configuration: IWindowConfiguration): TPromise<void> {
6265
filesToDiff
6366
};
6467

65-
// Resolve workspace
66-
return getWorkspace(configuration.workspacePath).then(workspace => {
67-
68-
// Open workbench
69-
return openWorkbench(configuration, workspace, shellOptions);
70-
});
68+
// Open workbench
69+
return openWorkbench(configuration, shellOptions);
7170
}
7271

7372
function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] {
@@ -95,12 +94,53 @@ function toInputs(paths: IPath[], isUntitledFile?: boolean): IResourceInput[] {
9594
});
9695
}
9796

98-
function getWorkspace(workspacePath: string): TPromise<Workspace> {
99-
if (!workspacePath) {
97+
function openWorkbench(configuration: IWindowConfiguration, options: IOptions): TPromise<void> {
98+
return getWorkspace(configuration).then(workspace => {
99+
const environmentService = new EnvironmentService(configuration, configuration.execPath);
100+
const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace);
101+
const timerService = new TimerService((<any>window).MonacoEnvironment.timers as IInitData, !workspaceConfigurationService.hasWorkspace());
102+
103+
return createStorageService(configuration, environmentService).then(storageService => {
104+
105+
// Since the configuration service is one of the core services that is used in so many places, we initialize it
106+
// right before startup of the workbench shell to have its data ready for consumers
107+
return workspaceConfigurationService.initialize().then(() => {
108+
timerService.beforeDOMContentLoaded = Date.now();
109+
110+
return domContentLoaded().then(() => {
111+
timerService.afterDOMContentLoaded = Date.now();
112+
113+
// Open Shell
114+
timerService.beforeWorkbenchOpen = Date.now();
115+
const shell = new WorkbenchShell(document.body, {
116+
contextService: workspaceConfigurationService,
117+
configurationService: workspaceConfigurationService,
118+
environmentService,
119+
timerService,
120+
storageService
121+
}, configuration, options);
122+
shell.open();
123+
124+
// Inform user about loading issues from the loader
125+
(<any>self).require.config({
126+
onError: (err: any) => {
127+
if (err.errorCode === 'load') {
128+
shell.onUnexpectedError(loaderError(err));
129+
}
130+
}
131+
});
132+
});
133+
});
134+
});
135+
});
136+
}
137+
138+
function getWorkspace(configuration: IWindowConfiguration): TPromise<Workspace> {
139+
if (!configuration.workspacePath) {
100140
return TPromise.as(null);
101141
}
102142

103-
return realpath(workspacePath).then(realWorkspacePath => {
143+
return realpath(configuration.workspacePath).then(realWorkspacePath => {
104144

105145
// for some weird reason, node adds a trailing slash to UNC paths
106146
// we never ever want trailing slashes as our workspace path unless
@@ -110,55 +150,44 @@ function getWorkspace(workspacePath: string): TPromise<Workspace> {
110150
realWorkspacePath = strings.rtrim(realWorkspacePath, paths.nativeSep);
111151
}
112152

153+
// update config
154+
configuration.workspacePath = realWorkspacePath;
155+
113156
const workspaceResource = uri.file(realWorkspacePath);
114157
const folderName = path.basename(realWorkspacePath) || realWorkspacePath;
115158

116-
return stat(realWorkspacePath).then(folderStat => {
117-
return new Workspace(
118-
workspaceResource,
119-
platform.isLinux ? folderStat.ino : folderStat.birthtime.getTime(),
120-
folderName // On Linux, birthtime is ctime, so we cannot use it! We use the ino instead!
121-
);
122-
});
159+
return new Workspace(workspaceResource, folderName);
123160
}, error => {
124161
errors.onUnexpectedError(error);
125162

126163
return null; // treat invalid paths as empty workspace
127164
});
128165
}
129166

130-
function openWorkbench(configuration: IWindowConfiguration, workspace: Workspace, options: IOptions): TPromise<void> {
131-
const environmentService = new EnvironmentService(configuration, configuration.execPath);
132-
const workspaceConfigurationService = new WorkspaceConfigurationService(environmentService, workspace);
133-
const timerService = new TimerService((<any>window).MonacoEnvironment.timers as IInitData, !workspaceConfigurationService.hasWorkspace());
134-
135-
// Since the configuration service is one of the core services that is used in so many places, we initialize it
136-
// right before startup of the workbench shell to have its data ready for consumers
137-
return workspaceConfigurationService.initialize().then(() => {
138-
timerService.beforeDOMContentLoaded = Date.now();
139-
140-
return domContentLoaded().then(() => {
141-
timerService.afterDOMContentLoaded = Date.now();
142-
143-
// Open Shell
144-
timerService.beforeWorkbenchOpen = Date.now();
145-
const shell = new WorkbenchShell(document.body, {
146-
contextService: workspaceConfigurationService,
147-
configurationService: workspaceConfigurationService,
148-
environmentService,
149-
timerService
150-
}, configuration, options);
151-
shell.open();
152-
153-
// Inform user about loading issues from the loader
154-
(<any>self).require.config({
155-
onError: (err: any) => {
156-
if (err.errorCode === 'load') {
157-
shell.onUnexpectedError(loaderError(err));
158-
}
159-
}
160-
});
161-
});
167+
function createStorageService(configuration: IWindowConfiguration, environmentService: IEnvironmentService): TPromise<IStorageService> {
168+
let workspaceStatPromise: TPromise<fs.Stats> = TPromise.as(null);
169+
if (configuration.workspacePath) {
170+
workspaceStatPromise = stat(configuration.workspacePath);
171+
}
172+
173+
return workspaceStatPromise.then(stat => {
174+
let id: IWorkspaceStorageIdentifier;
175+
if (stat) {
176+
id = { resource: uri.file(configuration.workspacePath), uid: platform.isLinux ? stat.ino : stat.birthtime.getTime() };
177+
} else if (configuration.backupPath) {
178+
// if we do not have a workspace open, we need to find another identifier for the window to store
179+
// workspace UI state. if we have a backup path in the configuration we can use that because this
180+
// will be a unique identifier per window that is stable between restarts as long as there are
181+
// dirty files in the workspace.
182+
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
183+
// scheme so that we can later distinguish these identifiers from the workspace one.
184+
id = { resource: uri.from({ path: path.basename(configuration.backupPath), scheme: 'empty' }) };
185+
}
186+
187+
const disableStorage = !!environmentService.extensionTestsPath; // never keep any state when running extension tests!
188+
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
189+
190+
return new StorageService(storage, storage, id);
162191
});
163192
}
164193

src/vs/workbench/electron-browser/shell.ts

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
2121
import pkg from 'vs/platform/node/package';
2222
import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService';
2323
import { Workbench, IWorkbenchStartedInfo } from 'vs/workbench/electron-browser/workbench';
24-
import { StorageService, inMemoryLocalStorageInstance } from 'vs/platform/storage/common/storageService';
2524
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2625
import { NullTelemetryService, configurationTelemetry, loadExperiments, lifecycleTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
2726
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
@@ -89,8 +88,6 @@ import { IURLService } from 'vs/platform/url/common/url';
8988
import { ExtensionHostProcessWorker } from 'vs/workbench/electron-browser/extensionHost';
9089
import { ITimerService } from 'vs/workbench/services/timer/common/timerService';
9190
import { remote, ipcRenderer as ipc } from 'electron';
92-
import URI from "vs/base/common/uri";
93-
import { basename } from "path";
9491
import { ITextMateService } from 'vs/editor/node/textMate/textMateService';
9592
import { MainProcessTextMateSyntax } from 'vs/editor/electron-browser/textMate/TMSyntax';
9693
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
@@ -110,6 +107,7 @@ export interface ICoreServices {
110107
configurationService: IConfigurationService;
111108
environmentService: IEnvironmentService;
112109
timerService: ITimerService;
110+
storageService: IStorageService;
113111
}
114112

115113
const currentWindow = remote.getCurrentWindow();
@@ -155,6 +153,7 @@ export class WorkbenchShell {
155153
this.configurationService = services.configurationService;
156154
this.environmentService = services.environmentService;
157155
this.timerService = services.timerService;
156+
this.storageService = services.storageService;
158157

159158
this.toUnbind = [];
160159
this.previousErrorTime = 0;
@@ -251,6 +250,7 @@ export class WorkbenchShell {
251250
serviceCollection.set(IConfigurationService, this.configurationService);
252251
serviceCollection.set(IEnvironmentService, this.environmentService);
253252
serviceCollection.set(ITimerService, this.timerService);
253+
serviceCollection.set(IStorageService, this.storageService);
254254

255255
const instantiationService: IInstantiationService = new InstantiationService(serviceCollection, true);
256256

@@ -273,22 +273,6 @@ export class WorkbenchShell {
273273
sharedProcess
274274
.done(client => client.registerChannel('choice', instantiationService.createInstance(ChoiceChannel)));
275275

276-
// Storage Sevice
277-
let workspaceIdentifier = this.contextService.getWorkspace();
278-
if (!workspaceIdentifier && !!this.configuration.backupPath) {
279-
// if we do not have a workspace open, we need to find another identifier for the window to store
280-
// workspace UI state. if we have a backup path in the configuration we can use that because this
281-
// will be a unique identifier per window that is stable between restarts as long as there are
282-
// dirty files in the workspace.
283-
// We use basename() to produce a short identifier, we do not need the full path. We use a custom
284-
// scheme so that we can later distinguish these identifiers from the workspace one.
285-
workspaceIdentifier = { resource: URI.from({ path: basename(this.configuration.backupPath), scheme: 'empty' }) };
286-
}
287-
const disableStorage = !!this.environmentService.extensionTestsPath; // never keep any state when running extension tests!
288-
const storage = disableStorage ? inMemoryLocalStorageInstance : window.localStorage;
289-
this.storageService = new StorageService(storage, storage, workspaceIdentifier);
290-
serviceCollection.set(IStorageService, this.storageService);
291-
292276
// Warm up font cache information before building up too many dom elements
293277
restoreFontInfo(this.storageService);
294278
readFontInfo(BareFontInfo.createFromRawSettings(this.configurationService.getConfiguration('editor'), browser.getZoomLevel()));

src/vs/workbench/services/configuration/node/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class WorkspaceConfigurationService extends Disposable implements IWorksp
8080
constructor(private environmentService: IEnvironmentService, private singleRootWorkspace?: SingleRootWorkspace, private workspaceSettingsRootFolder: string = WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME) {
8181
super();
8282

83-
this.workspace = singleRootWorkspace ? new Workspace(`${singleRootWorkspace.uid}`, [singleRootWorkspace.resource]) : null;
83+
this.workspace = singleRootWorkspace ? new Workspace(singleRootWorkspace.resource.toString(), [singleRootWorkspace.resource]) : null;
8484

8585
this.workspaceFilePathToConfiguration = Object.create(null);
8686
this.cachedConfig = new ConfigModel<any>(null);

0 commit comments

Comments
 (0)