提交 288445a5 编写于 作者: S Sandeep Somavarapu

#94599 Move exe based tips to new service in shared process

上级 9fdfbbc3
......@@ -13,8 +13,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ParsedArgs } from 'vs/platform/environment/node/argv';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
......@@ -69,6 +69,7 @@ import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/plat
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
export interface ISharedProcessConfiguration {
readonly machineId: string;
......@@ -190,6 +191,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService));
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService));
......@@ -218,6 +220,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
server.registerChannel('diagnostics', diagnosticsChannel);
const extensionTipsService = accessor.get(IExtensionTipsService);
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
server.registerChannel('extensionTipsService', extensionTipsChannel);
const authTokenService = accessor.get(IAuthenticationTokenService);
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
......
......@@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { URI } from 'vs/base/common/uri';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IExeBasedExtensionTip } from 'vs/platform/product/common/productService';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
......@@ -221,6 +222,18 @@ export interface IGlobalExtensionEnablementService {
}
export type IExecutableBasedExtensionTip = { extensionId: string } & Omit<Omit<IExeBasedExtensionTip, 'recommendations'>, 'important'>;
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('IExtensionTipsService');
export interface IExtensionTipsService {
_serviceBrand: undefined;
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]>;
}
export const ExtensionsLabel = localize('extensions', "Extensions");
export const ExtensionsChannelId = 'extensions';
export const PreferencesLabel = localize('preferences', "Preferences");
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
......@@ -130,3 +130,22 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
}
export class ExtensionTipsChannel implements IServerChannel {
constructor(private service: IExtensionTipsService) {
}
listen(context: any, event: string): Event<any> {
throw new Error('Invalid listen');
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'getImportantExecutableBasedTips': return this.service.getImportantExecutableBasedTips();
case 'getOtherExecutableBasedTips': return this.service.getOtherExecutableBasedTips();
}
throw new Error('Invalid call');
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { join, } from 'vs/base/common/path';
import { IProductService, IExeBasedExtensionTip } from 'vs/platform/product/common/productService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { env as processEnv } from 'vs/base/common/process';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IFileService } from 'vs/platform/files/common/files';
import { isWindows } from 'vs/base/common/platform';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IStringDictionary, forEach } from 'vs/base/common/collections';
export class ExtensionTipsService implements IExtensionTipsService {
_serviceBrand: any;
private readonly allImportantExecutableTips: IStringDictionary<IExeBasedExtensionTip> = {};
private readonly allOtherExecutableTips: IStringDictionary<IExeBasedExtensionTip> = {};
constructor(
@IFileService private readonly fileService: IFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IProductService private readonly productService: IProductService,
) {
if (this.productService.exeBasedExtensionTips) {
forEach(this.productService.exeBasedExtensionTips, ({ key, value }) => {
if (value.important) {
this.allImportantExecutableTips[key] = value;
} else {
this.allOtherExecutableTips[key] = value;
}
});
}
}
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.getValidExecutableBasedExtensionTips(this.allImportantExecutableTips);
}
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips);
}
private async getValidExecutableBasedExtensionTips(executableTips: IStringDictionary<IExeBasedExtensionTip>): Promise<IExecutableBasedExtensionTip[]> {
const result: IExecutableBasedExtensionTip[] = [];
const checkedExecutables: Map<string, boolean> = new Map<string, boolean>();
for (const exeName of Object.keys(executableTips)) {
const extensionTip = executableTips[exeName];
if (!isNonEmptyArray(extensionTip?.recommendations)) {
continue;
}
const exePaths: string[] = [];
if (isWindows) {
if (extensionTip.windowsPath) {
exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!)
.replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!)
.replace('%ProgramFiles%', processEnv['ProgramFiles']!)
.replace('%APPDATA%', processEnv['APPDATA']!)
.replace('%WINDIR%', processEnv['WINDIR']!));
}
} else {
exePaths.push(join('/usr/local/bin', exeName));
exePaths.push(join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName));
}
for (const exePath of exePaths) {
let exists = checkedExecutables.get(exePath);
if (exists === undefined) {
exists = await this.fileService.exists(URI.file(exePath));
checkedExecutables.set(exePath, exists);
}
if (exists) {
extensionTip.recommendations.forEach(recommendation => result.push({
extensionId: recommendation,
friendlyName: extensionTip.friendlyName,
exeFriendlyName: extensionTip.exeFriendlyName,
windowsPath: extensionTip.windowsPath,
}));
}
}
}
return result;
}
}
......@@ -5,11 +5,10 @@
import { ExtensionRecommendationsService, milliSecondsInADay, choiceNever } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService';
import { IExtensionRecommendationsService, IWorkbenchExtensionEnablementService, ExtensionRecommendationReason, IExtensionRecommendation, ExtensionRecommendationSource } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { join, basename } from 'vs/base/common/path';
import { basename } from 'vs/base/common/path';
import { distinct, shuffle } from 'vs/base/common/arrays';
import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService';
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IProductService } from 'vs/platform/product/common/productService';
import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
......@@ -31,10 +30,8 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { InstallRecommendedExtensionAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { forEach } from 'vs/base/common/collections';
import { platform, env as processEnv } from 'vs/base/common/process';
import { forEach, IStringDictionary } from 'vs/base/common/collections';
import { isNumber } from 'vs/base/common/types';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
interface IDynamicWorkspaceRecommendations {
remoteSet: string[];
......@@ -43,8 +40,7 @@ interface IDynamicWorkspaceRecommendations {
export class NativeExtensionRecommendationsService extends ExtensionRecommendationsService implements IExtensionRecommendationsService {
private _exeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null);
private _importantExeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null);
private _exeBasedRecommendations: { [id: string]: IExecutableBasedExtensionTip; } = Object.create(null);
private proactiveRecommendationsFetched: boolean = false;
private _extensionsRecommendationsUrl: string | undefined;
private _dynamicWorkspaceRecommendations: string[] = [];
......@@ -71,6 +67,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
@IProductService productService: IProductService,
@IRequestService private readonly requestService: IRequestService,
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
) {
super(galleryService, modelService, storageService, extensionsService, extensionEnablementService, instantiationService, fileService, contextService,
configurationService, telemetryService, environmentService, extensionService, viewletService, notificationService, extensionManagementService, extensionWorkbenchService,
......@@ -111,14 +108,15 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
if (!this.proactiveRecommendationsFetched) {
this.proactiveRecommendationsFetched = true;
// Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected
fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false)]));
fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchOtherExeBasedRecommendations()]));
}
return fetchPromise;
}
private async fetchImportantExeBasedRecommendation(): Promise<void> {
await this.fetchExecutableRecommendations(true);
await this.promptForImportantExeBasedExtension();
const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips();
importantExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
await this.promptForImportantExeBasedExtension(importantExectuableBasedTips);
}
/**
......@@ -209,13 +207,16 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
}
}
private async promptForImportantExeBasedExtension(): Promise<boolean> {
private async promptForImportantExeBasedExtension(importantExectuableBasedTips: IExecutableBasedExtensionTip[]): Promise<boolean> {
let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations);
const importantExeBasedRecommendations: IStringDictionary<IExecutableBasedExtensionTip> = {};
importantExectuableBasedTips.forEach(tip => importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
let recommendationsToSuggest = Object.keys(importantExeBasedRecommendations);
const installed = await this.extensionManagementService.getInstalled(ExtensionType.User);
recommendationsToSuggest = this.filterInstalled(recommendationsToSuggest, installed, (extensionId) => {
const tip = this._importantExeBasedRecommendations[extensionId];
const tip = importantExeBasedRecommendations[extensionId];
/* __GDPR__
"exeExtensionRecommendations:alreadyInstalled" : {
......@@ -232,7 +233,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
}
for (const extensionId of recommendationsToSuggest) {
const tip = this._importantExeBasedRecommendations[extensionId];
const tip = importantExeBasedRecommendations[extensionId];
/* __GDPR__
"exeExtensionRecommendations:notInstalled" : {
......@@ -259,7 +260,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
}
const extensionId = recommendationsToSuggest[0];
const tip = this._importantExeBasedRecommendations[extensionId];
const tip = importantExeBasedRecommendations[extensionId];
const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!));
this.notificationService.prompt(Severity.Info, message,
......@@ -332,59 +333,9 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati
return true;
}
/**
* If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations
*/
private async fetchExecutableRecommendations(important: boolean): Promise<void> {
if (!this.productService.exeBasedExtensionTips) {
return;
}
const foundExecutables: Set<string> = new Set<string>();
const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => {
return this.fileService.exists(URI.file(path)).then(exists => {
if (exists && !foundExecutables.has(exeName)) {
foundExecutables.add(exeName);
(tip['recommendations'] || []).forEach(extensionId => {
if (tip.friendlyName) {
if (important) {
this._importantExeBasedRecommendations[extensionId.toLowerCase()] = tip;
}
this._exeBasedRecommendations[extensionId.toLowerCase()] = tip;
}
});
}
});
};
const promises: Promise<void>[] = [];
// Loop through recommended extensions
forEach(this.productService.exeBasedExtensionTips, entry => {
if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) {
return;
}
if (important !== !!entry.value.important) {
return;
}
const exeName = entry.key;
if (platform === 'win32') {
let windowsPath = entry.value['windowsPath'];
if (!windowsPath || typeof windowsPath !== 'string') {
return;
}
windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!)
.replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!)
.replace('%ProgramFiles%', processEnv['ProgramFiles']!)
.replace('%APPDATA%', processEnv['APPDATA']!)
.replace('%WINDIR%', processEnv['WINDIR']!);
promises.push(findExecutable(exeName, entry.value, windowsPath));
} else {
promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName)));
promises.push(findExecutable(exeName, entry.value, join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName)));
}
});
await Promise.all(promises);
private async fetchOtherExeBasedRecommendations(): Promise<void> {
const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips();
otherExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip);
}
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
class WebExtensionTipsService implements IExtensionTipsService {
_serviceBrand: any;
constructor() {
}
async getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return [];
}
async getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return [];
}
}
registerSingleton(IExtensionTipsService, WebExtensionTipsService);
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement';
class NativeExtensionTipsService implements IExtensionTipsService {
_serviceBrand: any;
private readonly channel: IChannel;
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
) {
this.channel = sharedProcessService.getChannel('extensionTipsService');
}
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.channel.call<IExecutableBasedExtensionTip[]>('getImportantExecutableBasedTips');
}
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.channel.call<IExecutableBasedExtensionTip[]>('getOtherExecutableBasedTips');
}
}
registerSingleton(IExtensionTipsService, NativeExtensionTipsService);
......@@ -39,6 +39,7 @@ import 'vs/workbench/services/keybinding/electron-browser/keybinding.contributio
import 'vs/workbench/services/extensions/electron-browser/extensionService';
import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService';
import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService';
import 'vs/workbench/services/extensionManagement/electron-browser/extensionTipsService';
import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import 'vs/workbench/services/telemetry/electron-browser/telemetryService';
import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService';
......
......@@ -34,6 +34,7 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService';
import 'vs/workbench/services/keybinding/browser/keymapService';
import 'vs/workbench/services/extensions/browser/extensionService';
import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService';
import 'vs/workbench/services/extensionManagement/common/extensionTipsService';
import 'vs/workbench/services/telemetry/browser/telemetryService';
import 'vs/workbench/services/configurationResolver/browser/configurationResolverService';
import 'vs/workbench/services/credentials/browser/credentialsService';
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册