desktop.main.ts 17.0 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';
J
Joao Moreno 已提交
54

B
Benjamin Pasero 已提交
55
class DesktopMain extends Disposable {
E
Erich Gamma 已提交
56

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

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

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

B
Benjamin Pasero 已提交
66
	private init(): void {
67

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

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

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

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

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

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

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

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

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

113 114
		await domContentLoaded();
		mark('willStartWorkbench');
B
Benjamin Pasero 已提交
115

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

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

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

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

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

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

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

		// 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())));
	}

147
	private onWindowResize(e: Event, retry: boolean, workbench: Workbench): void {
148 149 150 151 152 153 154 155
		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) {
156
					scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false, workbench));
157 158 159 160
				}
				return;
			}

161
			workbench.layout();
162 163 164
		}
	}

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

168 169 170 171 172 173 174 175 176 177 178 179 180

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// 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
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

181

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

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

190
		// Product
191
		serviceCollection.set(IProductService, this.productService);
192

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

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

201 202 203 204 205 206 207 208 209 210 211 212 213 214

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// 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 已提交
215 216 217 218
		// Sign
		const signService = new SignService();
		serviceCollection.set(ISignService, signService);

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

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

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

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

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

237 238 239 240 241 242 243 244 245 246 247 248 249 250

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// 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
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


251 252
		const connection = remoteAgentService.getConnection();
		if (connection) {
253
			const remoteFileSystemProvider = this._register(new RemoteFileSystemProvider(remoteAgentService));
B
Benjamin Pasero 已提交
254
			fileService.registerProvider(Schemas.vscodeRemote, remoteFileSystemProvider);
255 256
		}

S
Sandeep Somavarapu 已提交
257
		const payload = await this.resolveWorkspaceInitializationPayload();
258 259

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

262 263
				// Workspace
				serviceCollection.set(IWorkspaceContextService, service);
B
Benjamin Pasero 已提交
264

265 266
				// Configuration
				serviceCollection.set(IConfigurationService, service);
B
Benjamin Pasero 已提交
267

268 269
				return service;
			}),
B
Benjamin Pasero 已提交
270

271
			this.createStorageService(payload, logService, mainProcessService).then(service => {
272 273 274 275 276 277

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

				return service;
			})
278 279
		]);

280 281 282 283 284 285 286 287 288 289 290 291 292 293

		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		//
		// 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
		//
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!


294
		return { serviceCollection, logService, storageService: services[1] };
295
	}
296

S
Sandeep Somavarapu 已提交
297
	private async resolveWorkspaceInitializationPayload(): Promise<IWorkspaceInitializationPayload> {
B
Benjamin Pasero 已提交
298 299

		// Multi-root workspace
300 301
		if (this.configuration.workspace) {
			return this.configuration.workspace;
302 303
		}

B
Benjamin Pasero 已提交
304
		// Single-folder workspace
305
		let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined;
306
		if (this.configuration.folderUri) {
S
Sandeep Somavarapu 已提交
307
			workspaceInitializationPayload = await this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri);
B
Benjamin Pasero 已提交
308
		}
E
Erich Gamma 已提交
309

310 311 312
		// Fallback to empty workspace if we have no payload yet.
		if (!workspaceInitializationPayload) {
			let id: string;
S
Sandeep Somavarapu 已提交
313 314
			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
315
			} else if (this.environmentService.isExtensionDevelopment) {
316 317 318
				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 已提交
319
			}
320

321 322 323 324
			workspaceInitializationPayload = { id };
		}

		return workspaceInitializationPayload;
B
Benjamin Pasero 已提交
325
	}
326

S
Sandeep Somavarapu 已提交
327
	private async resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise<ISingleFolderWorkspaceInitializationPayload | undefined> {
328
		try {
329 330 331
			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 已提交
332
			const id = await this.createHash(folderUri);
333
			return { id, folder };
334 335 336 337
		} catch (error) {
			onUnexpectedError(error);
		}
		return;
B
Benjamin Pasero 已提交
338
	}
339

S
Sandeep Somavarapu 已提交
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
	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');
	}

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

368 369 370 371 372
		try {
			await workspaceService.initialize(payload);

			return workspaceService;
		} catch (error) {
B
Benjamin Pasero 已提交
373 374
			onUnexpectedError(error);
			logService.error(error);
375

B
Benjamin Pasero 已提交
376
			return workspaceService;
377
		}
B
Benjamin Pasero 已提交
378
	}
379

380
	private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
381
		const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage'));
382
		const storageService = new NativeStorageService(globalStorageDatabase, logService, this.environmentService);
383

384 385 386 387 388
		try {
			await storageService.initialize(payload);

			return storageService;
		} catch (error) {
B
Benjamin Pasero 已提交
389 390
			onUnexpectedError(error);
			logService.error(error);
J
Joao Moreno 已提交
391

B
Benjamin Pasero 已提交
392
			return storageService;
393
		}
B
Benjamin Pasero 已提交
394
	}
395

396 397
}

398
export function main(configuration: INativeWorkbenchConfiguration): Promise<void> {
399
	const workbench = new DesktopMain(configuration);
400

401
	return workbench.open();
402
}