Skip to content

Commit fe4accb

Browse files
committed
microsoft#40233 Introduce resource identity service
- Service that provides an id for a given resource - Use for workspace folder identifier
1 parent 4749465 commit fe4accb

4 files changed

Lines changed: 99 additions & 46 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
7+
import { URI } from 'vs/base/common/uri';
8+
import { hash } from 'vs/base/common/hash';
9+
import { Disposable } from 'vs/base/common/lifecycle';
10+
11+
export const IResourceIdentityService = createDecorator<IResourceIdentityService>('IResourceIdentityService');
12+
export interface IResourceIdentityService {
13+
_serviceBrand: undefined;
14+
resolveResourceIdentity(resource: URI): Promise<string>;
15+
}
16+
17+
export class WebResourceIdentityService extends Disposable implements IResourceIdentityService {
18+
_serviceBrand: undefined;
19+
async resolveResourceIdentity(resource: URI): Promise<string> {
20+
return hash(resource.toString()).toString(16);
21+
}
22+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { createHash } from 'crypto';
7+
import { stat } from 'vs/base/node/pfs';
8+
import { Schemas } from 'vs/base/common/network';
9+
import { URI } from 'vs/base/common/uri';
10+
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
11+
import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
12+
import { Disposable } from 'vs/base/common/lifecycle';
13+
import { ResourceMap } from 'vs/base/common/map';
14+
15+
export class NativeResourceIdentityService extends Disposable implements IResourceIdentityService {
16+
17+
_serviceBrand: undefined;
18+
19+
private readonly cache: ResourceMap<Promise<string>> = new ResourceMap<Promise<string>>();
20+
21+
resolveResourceIdentity(resource: URI): Promise<string> {
22+
let promise = this.cache.get(resource);
23+
if (!promise) {
24+
promise = this.createIdentity(resource);
25+
this.cache.set(resource, promise);
26+
}
27+
return promise;
28+
}
29+
30+
private async createIdentity(resource: URI): Promise<string> {
31+
// Return early the folder is not local
32+
if (resource.scheme !== Schemas.file) {
33+
return createHash('md5').update(resource.toString()).digest('hex');
34+
}
35+
36+
const fileStat = await stat(resource.fsPath);
37+
let ctime: number | undefined;
38+
if (isLinux) {
39+
ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
40+
} else if (isMacintosh) {
41+
ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
42+
} else if (isWindows) {
43+
if (typeof fileStat.birthtimeMs === 'number') {
44+
ctime = Math.floor(fileStat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
45+
} else {
46+
ctime = fileStat.birthtime.getTime();
47+
}
48+
}
49+
50+
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
51+
// deleted and recreated. in that case we do not want to carry over previous state
52+
return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex');
53+
}
54+
}

src/vs/workbench/browser/web.main.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { WorkspaceService } from 'vs/workbench/services/configuration/browser/co
3333
import { ConfigurationCache } from 'vs/workbench/services/configuration/browser/configurationCache';
3434
import { ISignService } from 'vs/platform/sign/common/sign';
3535
import { SignService } from 'vs/platform/sign/browser/signService';
36-
import { hash } from 'vs/base/common/hash';
3736
import { IWorkbenchConstructionOptions, IWorkspace } from 'vs/workbench/workbench.web.api';
3837
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
3938
import { BACKUPS } from 'vs/platform/environment/common/environment';
@@ -51,6 +50,7 @@ import { isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/wi
5150
import { getWorkspaceIdentifier } from 'vs/workbench/services/workspaces/browser/workspaces';
5251
import { coalesce } from 'vs/base/common/arrays';
5352
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
53+
import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
5454

5555
class BrowserMain extends Disposable {
5656

@@ -157,7 +157,11 @@ class BrowserMain extends Disposable {
157157
const logService = new BufferLogService(this.configuration.logLevel);
158158
serviceCollection.set(ILogService, logService);
159159

160-
const payload = this.resolveWorkspaceInitializationPayload();
160+
// Resource Identity
161+
const resourceIdentityService = this._register(new WebResourceIdentityService());
162+
serviceCollection.set(IResourceIdentityService, resourceIdentityService);
163+
164+
const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService);
161165

162166
// Environment
163167
const environmentService = new BrowserWorkbenchEnvironmentService({ workspaceId: payload.id, logsPath, ...this.configuration });
@@ -292,7 +296,7 @@ class BrowserMain extends Disposable {
292296
}
293297
}
294298

295-
private resolveWorkspaceInitializationPayload(): IWorkspaceInitializationPayload {
299+
private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise<IWorkspaceInitializationPayload> {
296300
let workspace: IWorkspace | undefined = undefined;
297301
if (this.configuration.workspaceProvider) {
298302
workspace = this.configuration.workspaceProvider.workspace;
@@ -305,7 +309,8 @@ class BrowserMain extends Disposable {
305309

306310
// Single-folder workspace
307311
if (workspace && isFolderToOpen(workspace)) {
308-
return { id: hash(workspace.folderUri.toString()).toString(16), folder: workspace.folderUri };
312+
const id = await resourceIdentityService.resolveResourceIdentity(workspace.folderUri);
313+
return { id, folder: workspace.folderUri };
309314
}
310315

311316
return { id: 'empty-window' };

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

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55

66
import * as fs from 'fs';
77
import * as gracefulFs from 'graceful-fs';
8-
import { createHash } from 'crypto';
98
import { webFrame } from 'electron';
109
import { importEntries, mark } from 'vs/base/common/performance';
1110
import { Workbench } from 'vs/workbench/browser/workbench';
1211
import { NativeWindow } from 'vs/workbench/electron-browser/window';
1312
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
1413
import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
1514
import { onUnexpectedError } from 'vs/base/common/errors';
16-
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
1715
import { URI } from 'vs/base/common/uri';
1816
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
1917
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
2018
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
2119
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
22-
import { stat } from 'vs/base/node/pfs';
2320
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
2421
import { INativeWindowConfiguration } from 'vs/platform/windows/node/window';
2522
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
@@ -51,6 +48,8 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file
5148
import { basename } from 'vs/base/common/resources';
5249
import { IProductService } from 'vs/platform/product/common/productService';
5350
import product from 'vs/platform/product/common/product';
51+
import { NativeResourceIdentityService } from 'vs/platform/resource/node/resourceIdentityServiceImpl';
52+
import { IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
5453

5554
class DesktopMain extends Disposable {
5655

@@ -214,7 +213,10 @@ class DesktopMain extends Disposable {
214213
fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
215214
}
216215

217-
const payload = await this.resolveWorkspaceInitializationPayload();
216+
const resourceIdentityService = this._register(new NativeResourceIdentityService());
217+
serviceCollection.set(IResourceIdentityService, resourceIdentityService);
218+
219+
const payload = await this.resolveWorkspaceInitializationPayload(resourceIdentityService);
218220

219221
const services = await Promise.all([
220222
this.createWorkspaceService(payload, fileService, remoteAgentService, logService).then(service => {
@@ -240,7 +242,7 @@ class DesktopMain extends Disposable {
240242
return { serviceCollection, logService, storageService: services[1] };
241243
}
242244

243-
private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
245+
private async resolveWorkspaceInitializationPayload(resourceIdentityService: IResourceIdentityService): Promise<IWorkspaceInitializationPayload> {
244246

245247
// Multi-root workspace
246248
if (this.environmentService.configuration.workspace) {
@@ -250,7 +252,7 @@ class DesktopMain extends Disposable {
250252
// Single-folder workspace
251253
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined;
252254
if (this.environmentService.configuration.folderUri) {
253-
workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri);
255+
workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.environmentService.configuration.folderUri, resourceIdentityService);
254256
}
255257

256258
// Fallback to empty workspace if we have no payload yet.
@@ -270,46 +272,16 @@ class DesktopMain extends Disposable {
270272
return workspaceInitializationPayload;
271273
}
272274

273-
private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
274-
275-
// Return early the folder is not local
276-
if (folderUri.scheme !== Schemas.file) {
277-
return { id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri };
278-
}
279-
280-
function computeLocalDiskFolderId(folder: URI, stat: fs.Stats): string {
281-
let ctime: number | undefined;
282-
if (isLinux) {
283-
ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
284-
} else if (isMacintosh) {
285-
ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is
286-
} else if (isWindows) {
287-
if (typeof stat.birthtimeMs === 'number') {
288-
ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897)
289-
} else {
290-
ctime = stat.birthtime.getTime();
291-
}
292-
}
293-
294-
// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
295-
// deleted and recreated. in that case we do not want to carry over previous state
296-
return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex');
297-
}
298-
299-
// For local: ensure path is absolute and exists
275+
private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier, resourceIdentityService: IResourceIdentityService): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
300276
try {
301-
const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd());
302-
const fileStat = await stat(sanitizedFolderPath);
303-
304-
const sanitizedFolderUri = URI.file(sanitizedFolderPath);
305-
return {
306-
id: computeLocalDiskFolderId(sanitizedFolderUri, fileStat),
307-
folder: sanitizedFolderUri
308-
};
277+
const folder = folderUri.scheme === Schemas.file
278+
? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute
279+
: folderUri;
280+
const id = await resourceIdentityService.resolveResourceIdentity(folderUri);
281+
return { id, folder };
309282
} catch (error) {
310283
onUnexpectedError(error);
311284
}
312-
313285
return;
314286
}
315287

0 commit comments

Comments
 (0)