Skip to content

Commit 288445a

Browse files
committed
microsoft#94599 Move exe based tips to new service in shared process
1 parent 9fdfbbc commit 288445a

9 files changed

Lines changed: 216 additions & 73 deletions

File tree

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
1313
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
1414
import { ParsedArgs } from 'vs/platform/environment/node/argv';
1515
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
16-
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
17-
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
16+
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
17+
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
1818
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
1919
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
2020
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -69,6 +69,7 @@ import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/plat
6969
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
7070
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
7171
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
72+
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
7273

7374
export interface ISharedProcessConfiguration {
7475
readonly machineId: string;
@@ -190,6 +191,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
190191
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
191192
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
192193
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
194+
services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService));
193195

194196
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
195197
services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService));
@@ -218,6 +220,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
218220
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
219221
server.registerChannel('diagnostics', diagnosticsChannel);
220222

223+
const extensionTipsService = accessor.get(IExtensionTipsService);
224+
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
225+
server.registerChannel('extensionTipsService', extensionTipsChannel);
226+
221227
const authTokenService = accessor.get(IAuthenticationTokenService);
222228
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
223229
server.registerChannel('authToken', authTokenChannel);

src/vs/platform/extensionManagement/common/extensionManagement.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1010
import { URI } from 'vs/base/common/uri';
1111
import { CancellationToken } from 'vs/base/common/cancellation';
1212
import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
13+
import { IExeBasedExtensionTip } from 'vs/platform/product/common/productService';
1314

1415
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$';
1516
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
@@ -221,6 +222,18 @@ export interface IGlobalExtensionEnablementService {
221222

222223
}
223224

225+
export type IExecutableBasedExtensionTip = { extensionId: string } & Omit<Omit<IExeBasedExtensionTip, 'recommendations'>, 'important'>;
226+
227+
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('IExtensionTipsService');
228+
export interface IExtensionTipsService {
229+
_serviceBrand: undefined;
230+
231+
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
232+
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
233+
}
234+
235+
236+
224237
export const ExtensionsLabel = localize('extensions', "Extensions");
225238
export const ExtensionsChannelId = 'extensions';
226239
export const PreferencesLabel = localize('preferences', "Preferences");

src/vs/platform/extensionManagement/common/extensionManagementIpc.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
7-
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
7+
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
88
import { Event } from 'vs/base/common/event';
99
import { URI, UriComponents } from 'vs/base/common/uri';
1010
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
@@ -130,3 +130,22 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
130130
return Promise.resolve(this.channel.call('getExtensionsReport'));
131131
}
132132
}
133+
134+
export class ExtensionTipsChannel implements IServerChannel {
135+
136+
constructor(private service: IExtensionTipsService) {
137+
}
138+
139+
listen(context: any, event: string): Event<any> {
140+
throw new Error('Invalid listen');
141+
}
142+
143+
call(context: any, command: string, args?: any): Promise<any> {
144+
switch (command) {
145+
case 'getImportantExecutableBasedTips': return this.service.getImportantExecutableBasedTips();
146+
case 'getOtherExecutableBasedTips': return this.service.getOtherExecutableBasedTips();
147+
}
148+
149+
throw new Error('Invalid call');
150+
}
151+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 { URI } from 'vs/base/common/uri';
7+
import { join, } from 'vs/base/common/path';
8+
import { IProductService, IExeBasedExtensionTip } from 'vs/platform/product/common/productService';
9+
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
10+
import { env as processEnv } from 'vs/base/common/process';
11+
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
12+
import { IFileService } from 'vs/platform/files/common/files';
13+
import { isWindows } from 'vs/base/common/platform';
14+
import { isNonEmptyArray } from 'vs/base/common/arrays';
15+
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
16+
import { IStringDictionary, forEach } from 'vs/base/common/collections';
17+
18+
export class ExtensionTipsService implements IExtensionTipsService {
19+
20+
_serviceBrand: any;
21+
22+
private readonly allImportantExecutableTips: IStringDictionary<IExeBasedExtensionTip> = {};
23+
private readonly allOtherExecutableTips: IStringDictionary<IExeBasedExtensionTip> = {};
24+
25+
constructor(
26+
@IFileService private readonly fileService: IFileService,
27+
@IEnvironmentService private readonly environmentService: IEnvironmentService,
28+
@IProductService private readonly productService: IProductService,
29+
) {
30+
if (this.productService.exeBasedExtensionTips) {
31+
forEach(this.productService.exeBasedExtensionTips, ({ key, value }) => {
32+
if (value.important) {
33+
this.allImportantExecutableTips[key] = value;
34+
} else {
35+
this.allOtherExecutableTips[key] = value;
36+
}
37+
});
38+
}
39+
}
40+
41+
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
42+
return this.getValidExecutableBasedExtensionTips(this.allImportantExecutableTips);
43+
}
44+
45+
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
46+
return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips);
47+
}
48+
49+
private async getValidExecutableBasedExtensionTips(executableTips: IStringDictionary<IExeBasedExtensionTip>): Promise<IExecutableBasedExtensionTip[]> {
50+
const result: IExecutableBasedExtensionTip[] = [];
51+
52+
const checkedExecutables: Map<string, boolean> = new Map<string, boolean>();
53+
for (const exeName of Object.keys(executableTips)) {
54+
const extensionTip = executableTips[exeName];
55+
if (!isNonEmptyArray(extensionTip?.recommendations)) {
56+
continue;
57+
}
58+
59+
const exePaths: string[] = [];
60+
if (isWindows) {
61+
if (extensionTip.windowsPath) {
62+
exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!)
63+
.replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!)
64+
.replace('%ProgramFiles%', processEnv['ProgramFiles']!)
65+
.replace('%APPDATA%', processEnv['APPDATA']!)
66+
.replace('%WINDIR%', processEnv['WINDIR']!));
67+
}
68+
} else {
69+
exePaths.push(join('/usr/local/bin', exeName));
70+
exePaths.push(join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName));
71+
}
72+
73+
for (const exePath of exePaths) {
74+
let exists = checkedExecutables.get(exePath);
75+
if (exists === undefined) {
76+
exists = await this.fileService.exists(URI.file(exePath));
77+
checkedExecutables.set(exePath, exists);
78+
}
79+
if (exists) {
80+
extensionTip.recommendations.forEach(recommendation => result.push({
81+
extensionId: recommendation,
82+
friendlyName: extensionTip.friendlyName,
83+
exeFriendlyName: extensionTip.exeFriendlyName,
84+
windowsPath: extensionTip.windowsPath,
85+
}));
86+
}
87+
}
88+
}
89+
90+
return result;
91+
}
92+
93+
}

src/vs/workbench/contrib/extensions/electron-browser/extensionRecommendationsService.ts

Lines changed: 21 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55

66
import { ExtensionRecommendationsService, milliSecondsInADay, choiceNever } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
77
import { IExtensionRecommendationsService, IWorkbenchExtensionEnablementService, ExtensionRecommendationReason, IExtensionRecommendation, ExtensionRecommendationSource } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
8-
import { URI } from 'vs/base/common/uri';
9-
import { join, basename } from 'vs/base/common/path';
8+
import { basename } from 'vs/base/common/path';
109
import { distinct, shuffle } from 'vs/base/common/arrays';
11-
import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService';
12-
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
10+
import { IProductService } from 'vs/platform/product/common/productService';
11+
import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
1312
import { IModelService } from 'vs/editor/common/services/modelService';
1413
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
1514
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -31,10 +30,8 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions';
3130
import { localize } from 'vs/nls';
3231
import Severity from 'vs/base/common/severity';
3332
import { InstallRecommendedExtensionAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
34-
import { forEach } from 'vs/base/common/collections';
35-
import { platform, env as processEnv } from 'vs/base/common/process';
33+
import { forEach, IStringDictionary } from 'vs/base/common/collections';
3634
import { isNumber } from 'vs/base/common/types';
37-
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
3835

3936
interface IDynamicWorkspaceRecommendations {
4037
remoteSet: string[];
@@ -43,8 +40,7 @@ interface IDynamicWorkspaceRecommendations {
4340

4441
export class NativeExtensionRecommendationsService extends ExtensionRecommendationsService implements IExtensionRecommendationsService {
4542

46-
private _exeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null);
47-
private _importantExeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null);
43+
private _exeBasedRecommendations: { [id: string]: IExecutableBasedExtensionTip; } = Object.create(null);
4844
private proactiveRecommendationsFetched: boolean = false;
4945
private _extensionsRecommendationsUrl: string | undefined;
5046
private _dynamicWorkspaceRecommendations: string[] = [];
@@ -71,6 +67,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
7167
@IProductService productService: IProductService,
7268
@IRequestService private readonly requestService: IRequestService,
7369
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
70+
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
7471
) {
7572
super(galleryService, modelService, storageService, extensionsService, extensionEnablementService, instantiationService, fileService, contextService,
7673
configurationService, telemetryService, environmentService, extensionService, viewletService, notificationService, extensionManagementService, extensionWorkbenchService,
@@ -111,14 +108,15 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
111108
if (!this.proactiveRecommendationsFetched) {
112109
this.proactiveRecommendationsFetched = true;
113110
// Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected
114-
fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false)]));
111+
fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchOtherExeBasedRecommendations()]));
115112
}
116113
return fetchPromise;
117114
}
118115

119116
private async fetchImportantExeBasedRecommendation(): Promise<void> {
120-
await this.fetchExecutableRecommendations(true);
121-
await this.promptForImportantExeBasedExtension();
117+
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
118+
importantExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
119+
await this.promptForImportantExeBasedExtension(importantExectuableBasedTips);
122120
}
123121

124122
/**
@@ -209,13 +207,16 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
209207
}
210208
}
211209

212-
private async promptForImportantExeBasedExtension(): Promise<boolean> {
210+
private async promptForImportantExeBasedExtension(importantExectuableBasedTips: IExecutableBasedExtensionTip[]): Promise<boolean> {
213211

214-
let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations);
212+
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
213+
importantExectuableBasedTips.forEach(tip => importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
214+
215+
let recommendationsToSuggest = Object.keys(importantExeBasedRecommendations);
215216

216217
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
217218
recommendationsToSuggest = this.filterInstalled(recommendationsToSuggest, installed, (extensionId) => {
218-
const tip = this._importantExeBasedRecommendations[extensionId];
219+
const tip = importantExeBasedRecommendations[extensionId];
219220

220221
/* __GDPR__
221222
"exeExtensionRecommendations:alreadyInstalled" : {
@@ -232,7 +233,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
232233
}
233234

234235
for (const extensionId of recommendationsToSuggest) {
235-
const tip = this._importantExeBasedRecommendations[extensionId];
236+
const tip = importantExeBasedRecommendations[extensionId];
236237

237238
/* __GDPR__
238239
"exeExtensionRecommendations:notInstalled" : {
@@ -259,7 +260,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
259260
}
260261

261262
const extensionId = recommendationsToSuggest[0];
262-
const tip = this._importantExeBasedRecommendations[extensionId];
263+
const tip = importantExeBasedRecommendations[extensionId];
263264
const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!));
264265

265266
this.notificationService.prompt(Severity.Info, message,
@@ -332,59 +333,9 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
332333
return true;
333334
}
334335

335-
/**
336-
* If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations
337-
*/
338-
private async fetchExecutableRecommendations(important: boolean): Promise<void> {
339-
if (!this.productService.exeBasedExtensionTips) {
340-
return;
341-
}
342-
343-
const foundExecutables: Set<string> = new Set<string>();
344-
const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => {
345-
return this.fileService.exists(URI.file(path)).then(exists => {
346-
if (exists && !foundExecutables.has(exeName)) {
347-
foundExecutables.add(exeName);
348-
(tip['recommendations'] || []).forEach(extensionId => {
349-
if (tip.friendlyName) {
350-
if (important) {
351-
this._importantExeBasedRecommendations[extensionId.toLowerCase()] = tip;
352-
}
353-
this._exeBasedRecommendations[extensionId.toLowerCase()] = tip;
354-
}
355-
});
356-
}
357-
});
358-
};
359-
360-
const promises: Promise<void>[] = [];
361-
// Loop through recommended extensions
362-
forEach(this.productService.exeBasedExtensionTips, entry => {
363-
if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) {
364-
return;
365-
}
366-
if (important !== !!entry.value.important) {
367-
return;
368-
}
369-
const exeName = entry.key;
370-
if (platform === 'win32') {
371-
let windowsPath = entry.value['windowsPath'];
372-
if (!windowsPath || typeof windowsPath !== 'string') {
373-
return;
374-
}
375-
windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!)
376-
.replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!)
377-
.replace('%ProgramFiles%', processEnv['ProgramFiles']!)
378-
.replace('%APPDATA%', processEnv['APPDATA']!)
379-
.replace('%WINDIR%', processEnv['WINDIR']!);
380-
promises.push(findExecutable(exeName, entry.value, windowsPath));
381-
} else {
382-
promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName)));
383-
promises.push(findExecutable(exeName, entry.value, join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName)));
384-
}
385-
});
386-
387-
await Promise.all(promises);
336+
private async fetchOtherExeBasedRecommendations(): Promise<void> {
337+
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
338+
otherExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
388339
}
389340

390341
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } {

0 commit comments

Comments
 (0)