desktop.main.ts 17.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as fs from 'fs';
7
import * as gracefulFs from 'graceful-fs';
S
Sandeep Somavarapu 已提交
8 9 10
import { createHash } from 'crypto';
import { stat } from 'vs/base/node/pfs';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
11
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
12
import { importEntries, mark } from 'vs/base/common/performance';
13
import { Workbench } from 'vs/workbench/browser/workbench';
14
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
15
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
16
import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
17
import { onUnexpectedError } from 'vs/base/common/errors';
18
import { URI } from 'vs/base/common/uri';
19
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
20
import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
21
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
22
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
23
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
24
import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
25
import { ILogService } from 'vs/platform/log/common/log';
26
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
27
import { Schemas } from 'vs/base/common/network';
B
Benjamin Pasero 已提交
28
import { sanitizeFilePath } from 'vs/base/common/extpath';
B
wip  
Benjamin Pasero 已提交
29
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
B
Benjamin Pasero 已提交
30 31 32
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
33
import { Disposable } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
34
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
35
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
36
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
37 38 39
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
40
import { FileService } from 'vs/platform/files/common/fileService';
41
import { IFileService } from 'vs/platform/files/common/files';
42
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
43
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
44
import { ConfigurationCache } from 'vs/workbench/services/configuration/electron-browser/configurationCache';
I
isidor 已提交
45 46
import { SignService } from 'vs/platform/sign/node/signService';
import { ISignService } from 'vs/platform/sign/common/sign';
S
Sandeep Somavarapu 已提交
47
import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider';
48
import { basename } from 'vs/base/common/resources';
49
import { IProductService } from 'vs/platform/product/common/productService';
50
import product from 'vs/platform/product/common/product';
B
Benjamin Pasero 已提交
51
import { NativeLogService } from 'vs/workbench/services/log/electron-browser/logService';
52 53
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
54 55
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
J
Joao Moreno 已提交
56

B
Benjamin Pasero 已提交
57
class DesktopMain extends Disposable {
E
Erich Gamma 已提交
58

59 60
	private readonly productService: IProductService = { _serviceBrand: undefined, ...product };
	private readonly environmentService = new NativeWorkbenchEnvironmentService(this.configuration, this.productService);
61

62
	constructor(private configuration: INativeWorkbenchConfiguration) {
B
Benjamin Pasero 已提交
63
		super();
B
Benjamin Pasero 已提交
64

B
Benjamin Pasero 已提交
65 66
		this.init();
	}
M
Martin Aeschlimann 已提交
67

B
Benjamin Pasero 已提交
68
	private init(): void {
69

B
Benjamin Pasero 已提交
70 71
		// Enable gracefulFs
		gracefulFs.gracefulify(fs);
72

B
Benjamin Pasero 已提交
73 74
		// Massage configuration file URIs
		this.reviveUris();
75

B
Benjamin Pasero 已提交
76
		// Setup perf
77
		importEntries(this.configuration.perfEntries);
A
Alex Dima 已提交
78

B
Benjamin Pasero 已提交
79
		// Browser config
80 81 82
		const zoomLevel = this.configuration.zoomLevel || 0;
		setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
		setZoomLevel(zoomLevel, true /* isTrusted */);
83
		setFullscreen(!!this.configuration.fullscreen);
M
Martin Aeschlimann 已提交
84
	}
85

B
Benjamin Pasero 已提交
86
	private reviveUris() {
87 88
		if (this.configuration.folderUri) {
			this.configuration.folderUri = URI.revive(this.configuration.folderUri);
B
Benjamin Pasero 已提交
89
		}
90

91 92
		if (this.configuration.workspace) {
			this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace);
M
Martin Aeschlimann 已提交
93
		}
B
Benjamin Pasero 已提交
94

95
		const filesToWait = this.configuration.filesToWait;
B
Benjamin Pasero 已提交
96
		const filesToWaitPaths = filesToWait?.paths;
97
		[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
B
Benjamin Pasero 已提交
98 99 100
			if (Array.isArray(paths)) {
				paths.forEach(path => {
					if (path.fileUri) {
101
						path.fileUri = URI.revive(path.fileUri);
B
Benjamin Pasero 已提交
102 103 104 105
					}
				});
			}
		});
106

M
Martin Aeschlimann 已提交
107
		if (filesToWait) {
108
			filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
M
Martin Aeschlimann 已提交
109
		}
B
Benjamin Pasero 已提交
110
	}
B
Benjamin Pasero 已提交
111

112 113
	async open(): Promise<void> {
		const services = await this.initServices();
B
Benjamin Pasero 已提交
114

115 116
		await domContentLoaded();
		mark('willStartWorkbench');
B
Benjamin Pasero 已提交
117

118
		// Create Workbench
119
		const workbench = new Workbench(document.body, services.serviceCollection, services.logService);
B
Benjamin Pasero 已提交
120

B
Benjamin Pasero 已提交
121 122
		// Listeners
		this.registerListeners(workbench, services.storageService);
123

124
		// Startup
125
		const instantiationService = workbench.startup();
B
Benjamin Pasero 已提交
126

127
		// Window
128
		this._register(instantiationService.createInstance(NativeWindow));
B
Benjamin Pasero 已提交
129

130
		// Driver
131
		if (this.configuration.driver) {
B
Benjamin Pasero 已提交
132
			instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId)));
133
		}
134

135
		// Logging
136
		services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
B
Benjamin Pasero 已提交
137
	}
138

139
	private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
B
Benjamin Pasero 已提交
140 141 142 143 144 145 146 147 148

		// Layout
		this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench)));

		// Workbench Lifecycle
		this._register(workbench.onShutdown(() => this.dispose()));
		this._register(workbench.onWillShutdown(event => event.join(storageService.close())));
	}

149
	private onWindowResize(e: Event, retry: boolean, workbench: Workbench): void {
150 151 152 153 154 155 156 157
		if (e.target === window) {
			if (window.document && window.document.body && window.document.body.clientWidth === 0) {
				// TODO@Ben this is an electron issue on macOS when simple fullscreen is enabled
				// where for some reason the window clientWidth is reported as 0 when switching
				// between simple fullscreen and normal screen. In that case we schedule the layout
				// call at the next animation frame once, in the hope that the dimensions are
				// proper then.
				if (retry) {
158
					scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false, workbench));
159 160 161 162
				}
				return;
			}

163
			workbench.layout();
164 165 166
		}
	}

S
Sandeep Somavarapu 已提交
167
	private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
B
Benjamin Pasero 已提交
168
		const serviceCollection = new ServiceCollection();
169

170 171 172 173 174 175 176 177 178 179 180 181 182

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// NOTE: Please do NOT register services here. Use `registerSingleton()`
		//       from `workbench.common.main.ts` if the service is shared between
		//       desktop and web or `workbench.sandbox.main.ts` if the service
		//       is desktop only.
		//
		//       DO NOT add services to `workbench.desktop.main.ts`, always add
		//       to `workbench.sandbox.main.ts` to support our Electron sandbox
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

183

184
		// Main Process
185 186
		const mainProcessService = this._register(new MainProcessService(this.configuration.windowId));
		serviceCollection.set(IMainProcessService, mainProcessService);
187

B
Benjamin Pasero 已提交
188
		// Environment
189
		serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService);
190
		serviceCollection.set(INativeWorkbenchEnvironmentService, this.environmentService);
B
Benjamin Pasero 已提交
191

192
		// Product
193
		serviceCollection.set(IProductService, this.productService);
194

B
Benjamin Pasero 已提交
195
		// Log
B
Benjamin Pasero 已提交
196
		const logService = this._register(new NativeLogService(this.configuration.windowId, mainProcessService, this.environmentService));
B
Benjamin Pasero 已提交
197 198
		serviceCollection.set(ILogService, logService);

199 200 201
		// Remote
		const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
		serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
B
Benjamin Pasero 已提交
202

203 204 205 206 207 208 209 210 211 212 213 214 215 216

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// NOTE: Please do NOT register services here. Use `registerSingleton()`
		//       from `workbench.common.main.ts` if the service is shared between
		//       desktop and web or `workbench.sandbox.main.ts` if the service
		//       is desktop only.
		//
		//       DO NOT add services to `workbench.desktop.main.ts`, always add
		//       to `workbench.sandbox.main.ts` to support our Electron sandbox
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


I
isidor 已提交
217 218 219 220
		// Sign
		const signService = new SignService();
		serviceCollection.set(ISignService, signService);

221
		// Remote Agent
222
		const remoteAgentService = this._register(new RemoteAgentService(this.environmentService, this.productService, remoteAuthorityResolverService, signService, logService));
223 224
		serviceCollection.set(IRemoteAgentService, remoteAgentService);

225 226 227
		// Native Host
		const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
		serviceCollection.set(INativeHostService, nativeHostService);
228

B
Benjamin Pasero 已提交
229
		// Files
B
Benjamin Pasero 已提交
230
		const fileService = this._register(new FileService(logService));
B
Benjamin Pasero 已提交
231 232
		serviceCollection.set(IFileService, fileService);

233
		const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService, nativeHostService));
234
		fileService.registerProvider(Schemas.file, diskFileSystemProvider);
B
Benjamin Pasero 已提交
235

236
		// User Data Provider
237
		fileService.registerProvider(Schemas.userData, new FileUserDataProvider(this.environmentService.appSettingsHome, this.configuration.backupPath ? URI.file(this.configuration.backupPath) : undefined, diskFileSystemProvider, this.environmentService, logService));
238

B
Benjamin Pasero 已提交
239
		// Uri Identity
240 241
		const uriIdentityService = new UriIdentityService(fileService);
		serviceCollection.set(IUriIdentityService, uriIdentityService);
242 243 244 245 246 247 248 249 250 251 252 253 254 255

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// NOTE: Please do NOT register services here. Use `registerSingleton()`
		//       from `workbench.common.main.ts` if the service is shared between
		//       desktop and web or `workbench.sandbox.main.ts` if the service
		//       is desktop only.
		//
		//       DO NOT add services to `workbench.desktop.main.ts`, always add
		//       to `workbench.sandbox.main.ts` to support our Electron sandbox
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


256 257
		const connection = remoteAgentService.getConnection();
		if (connection) {
258
			const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
B
Benjamin Pasero 已提交
259
			fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
260 261
		}

S
Sandeep Somavarapu 已提交
262
		const payload = await this.resolveWorkspaceInitializationPayload();
263 264

		const services = await Promise.all([
265
			this.createWorkspaceService(payload, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
B
Benjamin Pasero 已提交
266

267 268
				// Workspace
				serviceCollection.set(IWorkspaceContextService, service);
B
Benjamin Pasero 已提交
269

270 271
				// Configuration
				serviceCollection.set(IConfigurationService, service);
B
Benjamin Pasero 已提交
272

273 274
				return service;
			}),
B
Benjamin Pasero 已提交
275

276
			this.createStorageService(payload, logService, mainProcessService).then(service => {
277 278 279 280 281 282

				// Storage
				serviceCollection.set(IStorageService, service);

				return service;
			})
283 284
		]);

285 286 287 288 289 290 291 292 293 294 295 296 297 298

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// NOTE: Please do NOT register services here. Use `registerSingleton()`
		//       from `workbench.common.main.ts` if the service is shared between
		//       desktop and web or `workbench.sandbox.main.ts` if the service
		//       is desktop only.
		//
		//       DO NOT add services to `workbench.desktop.main.ts`, always add
		//       to `workbench.sandbox.main.ts` to support our Electron sandbox
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


299
		return { serviceCollection, logService, storageService: services[1] };
300
	}
301

S
Sandeep Somavarapu 已提交
302
	private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
B
Benjamin Pasero 已提交
303 304

		// Multi-root workspace
305 306
		if (this.configuration.workspace) {
			return this.configuration.workspace;
307 308
		}

B
Benjamin Pasero 已提交
309
		// Single-folder workspace
310
		let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined;
311
		if (this.configuration.folderUri) {
S
Sandeep Somavarapu 已提交
312
			workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri);
B
Benjamin Pasero 已提交
313
		}
E
Erich Gamma 已提交
314

315 316 317
		// Fallback to empty workspace if we have no payload yet.
		if (!workspaceInitializationPayload) {
			let id: string;
S
Sandeep Somavarapu 已提交
318 319
			if (this.environmentService.backupWorkspaceHome) {
				id = basename(this.environmentService.backupWorkspaceHome); // we know the backupPath must be a unique path so we leverage its name as workspace ID
320
			} else if (this.environmentService.isExtensionDevelopment) {
321 322 323
				id = 'ext-dev'; // extension development window never stores backups and is a singleton
			} else {
				throw new Error('Unexpected window configuration without backupPath');
B
Benjamin Pasero 已提交
324
			}
325

326 327 328 329
			workspaceInitializationPayload = { id };
		}

		return workspaceInitializationPayload;
B
Benjamin Pasero 已提交
330
	}
331

S
Sandeep Somavarapu 已提交
332
	private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
333
		try {
334 335 336
			const folder = folderUri.scheme === Schemas.file
				? URI.file(sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd())) // For local: ensure path is absolute
				: folderUri;
S
Sandeep Somavarapu 已提交
337
			const id = await this.createHash(folderUri);
338
			return { id, folder };
339 340 341 342
		} catch (error) {
			onUnexpectedError(error);
		}
		return;
B
Benjamin Pasero 已提交
343
	}
344

S
Sandeep Somavarapu 已提交
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
	private async createHash(resource: URI): Promise<string> {
		// Return early the folder is not local
		if (resource.scheme !== Schemas.file) {
			return createHash('md5').update(resource.toString()).digest('hex');
		}

		const fileStat = await stat(resource.fsPath);
		let ctime: number | undefined;
		if (isLinux) {
			ctime = fileStat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead!
		} else if (isMacintosh) {
			ctime = fileStat.birthtime.getTime(); // macOS: birthtime is fine to use as is
		} else if (isWindows) {
			if (typeof fileStat.birthtimeMs === 'number') {
				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)
			} else {
				ctime = fileStat.birthtime.getTime();
			}
		}

		// we use the ctime as extra salt to the ID so that we catch the case of a folder getting
		// deleted and recreated. in that case we do not want to carry over previous state
		return createHash('md5').update(resource.fsPath).update(ctime ? String(ctime) : '').digest('hex');
	}

370 371
	private async createWorkspaceService(payload: IWorkspaceInitializationPayload, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
		const workspaceService = new WorkspaceService({ remoteAuthority: this.environmentService.remoteAuthority, configurationCache: new ConfigurationCache(this.environmentService) }, this.environmentService, fileService, remoteAgentService, uriIdentityService, logService);
S
Sandeep Somavarapu 已提交
372

373 374 375 376 377
		try {
			await workspaceService.initialize(payload);

			return workspaceService;
		} catch (error) {
B
Benjamin Pasero 已提交
378 379
			onUnexpectedError(error);
			logService.error(error);
380

B
Benjamin Pasero 已提交
381
			return workspaceService;
382
		}
B
Benjamin Pasero 已提交
383
	}
384

385
	private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
386
		const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage'));
387
		const storageService = new NativeStorageService(globalStorageDatabase, logService, this.environmentService);
388

389 390 391 392 393
		try {
			await storageService.initialize(payload);

			return storageService;
		} catch (error) {
B
Benjamin Pasero 已提交
394 395
			onUnexpectedError(error);
			logService.error(error);
J
Joao Moreno 已提交
396

B
Benjamin Pasero 已提交
397
			return storageService;
398
		}
B
Benjamin Pasero 已提交
399
	}
400

401 402
}

403
export function main(configuration: INativeWorkbenchConfiguration): Promise<void> {
404
	const workbench = new DesktopMain(configuration);
405

406
	return workbench.open();
407
}