diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index b158b4299b4..f54a1553738 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -104,7 +104,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { UserDataProfilesCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/userDataProfilesCleaner'; @@ -249,7 +249,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService)); + const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home), mainProcessService.getChannel('userDataProfiles'))); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 0fde9407ded..d4b70d0f518 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -654,12 +654,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract install(vsix: URI, options?: InstallVSIXOptions): Promise; abstract installFromLocation(location: URI, profileLocation: URI): Promise; abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; + abstract copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise; abstract reinstallFromGallery(extension: ILocalExtension): Promise; abstract cleanUp(): Promise; abstract onDidUpdateExtensionMetadata: Event; - abstract getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise; abstract updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; protected abstract getCurrentExtensionsManifestLocation(): URI; diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index c5f01490d8b..d04e46e79cb 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -458,8 +458,7 @@ export interface IExtensionManagementService { reinstallFromGallery(extension: ILocalExtension): Promise; getInstalled(type?: ExtensionType, profileLocation?: URI): Promise; getExtensionsControlManifest(): Promise; - - getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise; + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise; updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise; download(extension: IGalleryExtension, operation: InstallOperation): Promise; diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 0e5dcbceffc..88ba111abf0 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; import { URI, UriComponents } from 'vs/base/common/uri'; import { DefaultURITransformer, IURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -13,8 +12,10 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionIdentifier, IExtensionTipsService, IGalleryExtension, ILocalExtension, IExtensionsControlManifest, isTargetPlatformCompatible, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, IExtensionManagementService, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI { - return URI.revive(transformer ? transformer.transformIncoming(uri) : uri); +function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI; +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined; +function transformIncomingURI(uri: UriComponents | undefined, transformer: IURITransformer | null): URI | undefined { + return uri ? URI.revive(transformer ? transformer.transformIncoming(uri) : uri) : undefined; } function transformOutgoingURI(uri: URI, transformer: IURITransformer | null): URI { @@ -28,6 +29,10 @@ function transformIncomingExtension(extension: ILocalExtension, transformer: IUR return { ...transformed, ...{ manifest } }; } +function transformIncomingOptions(options: O | undefined, transformer: IURITransformer | null): O | undefined { + return options?.profileLocation ? transformAndReviveIncomingURIs(options, transformer ?? DefaultURITransformer) : options; +} + function transformOutgoingExtension(extension: ILocalExtension, transformer: IURITransformer | null): ILocalExtension { return transformer ? cloneAndChange(extension, value => value instanceof URI ? transformer.transformOutgoingURI(value) : undefined) : extension; } @@ -51,41 +56,109 @@ export class ExtensionManagementChannel implements IServerChannel { listen(context: any, event: string): Event { const uriTransformer = this.getUriTransformer(context); switch (event) { - case 'onInstallExtension': return this.onInstallExtension; - case 'onDidInstallExtensions': return Event.map(this.onDidInstallExtensions, results => results.map(i => ({ ...i, local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local }))); - case 'onUninstallExtension': return this.onUninstallExtension; - case 'onDidUninstallExtension': return this.onDidUninstallExtension; - case 'onDidUpdateExtensionMetadata': return this.onDidUpdateExtensionMetadata; + case 'onInstallExtension': { + return Event.map(this.onInstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidInstallExtensions': { + return Event.map(this.onDidInstallExtensions, results => + results.map(i => ({ + ...i, + local: i.local ? transformOutgoingExtension(i.local, uriTransformer) : i.local, + profileLocation: i.profileLocation ? transformOutgoingURI(i.profileLocation, uriTransformer) : i.profileLocation + }))); + } + case 'onUninstallExtension': { + return Event.map(this.onUninstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidUninstallExtension': { + return Event.map(this.onDidUninstallExtension, e => { + return { + ...e, + profileLocation: e.profileLocation ? transformOutgoingURI(e.profileLocation, uriTransformer) : e.profileLocation + }; + }); + } + case 'onDidUpdateExtensionMetadata': { + return Event.map(this.onDidUpdateExtensionMetadata, e => transformOutgoingExtension(e, uriTransformer)); + } } throw new Error('Invalid listen'); } - call(context: any, command: string, args?: any): Promise { + async call(context: any, command: string, args?: any): Promise { const uriTransformer: IURITransformer | null = this.getUriTransformer(context); switch (command) { - case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer)); - case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer)); - case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer), revive(args[1])); - case 'installFromLocation': return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), URI.revive(args[1])); - case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); - case 'getTargetPlatform': return this.service.getTargetPlatform(); - case 'canInstall': return this.service.canInstall(args[0]); - case 'installFromGallery': return this.service.installFromGallery(args[0], revive(args[1])); - case 'uninstall': return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), revive(args[1])); - case 'reinstallFromGallery': return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); - case 'getInstalled': return this.service.getInstalled(args[0], URI.revive(args[1])).then(extensions => extensions.map(e => transformOutgoingExtension(e, uriTransformer))); - case 'getMetadata': return this.service.getMetadata(transformIncomingExtension(args[0], uriTransformer), URI.revive(args[1])); - case 'updateMetadata': return this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], URI.revive(args[2])).then(e => transformOutgoingExtension(e, uriTransformer)); - case 'getExtensionsControlManifest': return this.service.getExtensionsControlManifest(); - case 'download': return this.service.download(args[0], args[1]); - case 'cleanUp': return this.service.cleanUp(); + case 'zip': { + const extension = transformIncomingExtension(args[0], uriTransformer); + const uri = await this.service.zip(extension); + return transformOutgoingURI(uri, uriTransformer); + } + case 'unzip': { + return this.service.unzip(transformIncomingURI(args[0], uriTransformer)); + } + case 'install': { + return this.service.install(transformIncomingURI(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); + } + case 'installFromLocation': { + return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); + } + case 'getManifest': { + return this.service.getManifest(transformIncomingURI(args[0], uriTransformer)); + } + case 'getTargetPlatform': { + return this.service.getTargetPlatform(); + } + case 'canInstall': { + return this.service.canInstall(args[0]); + } + case 'installFromGallery': { + return this.service.installFromGallery(args[0], transformIncomingOptions(args[1], uriTransformer)); + } + case 'uninstall': { + return this.service.uninstall(transformIncomingExtension(args[0], uriTransformer), transformIncomingOptions(args[1], uriTransformer)); + } + case 'reinstallFromGallery': { + return this.service.reinstallFromGallery(transformIncomingExtension(args[0], uriTransformer)); + } + case 'getInstalled': { + const extensions = await this.service.getInstalled(args[0], transformIncomingURI(args[1], uriTransformer)); + return extensions.map(e => transformOutgoingExtension(e, uriTransformer)); + } + case 'copyExtensions': { + return this.service.copyExtensions(transformIncomingURI(args[0], uriTransformer), transformIncomingURI(args[1], uriTransformer)); + } + case 'updateMetadata': { + const e = await this.service.updateMetadata(transformIncomingExtension(args[0], uriTransformer), args[1], transformIncomingURI(args[2], uriTransformer)); + return transformOutgoingExtension(e, uriTransformer); + } + case 'getExtensionsControlManifest': { + return this.service.getExtensionsControlManifest(); + } + case 'download': { + return this.service.download(args[0], args[1]); + } + case 'cleanUp': { + return this.service.cleanUp(); + } } throw new Error('Invalid call'); } } +export type ExtensionEventResult = InstallExtensionEvent | InstallExtensionResult | UninstallExtensionEvent | DidUninstallExtensionEvent; + export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService { declare readonly _serviceBrand: undefined; @@ -107,13 +180,23 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt constructor(private readonly channel: IChannel) { super(); - this._register(this.channel.listen('onInstallExtension')(e => this._onInstallExtension.fire({ identifier: e.identifier, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))); - this._register(this.channel.listen('onDidInstallExtensions')(results => this._onDidInstallExtensions.fire(results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))))); - this._register(this.channel.listen('onUninstallExtension')(e => this._onUninstallExtension.fire({ identifier: e.identifier, profileLocation: URI.revive(e.profileLocation) }))); - this._register(this.channel.listen('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire({ ...e, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onInstallExtension')(e => this.fireEvent(this._onInstallExtension, { ...e, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onDidInstallExtensions')(results => this.fireEvent(this._onDidInstallExtensions, results.map(e => ({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local, source: this.isUriComponents(e.source) ? URI.revive(e.source) : e.source, profileLocation: URI.revive(e.profileLocation) }))))); + this._register(this.channel.listen('onUninstallExtension')(e => this.fireEvent(this._onUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); + this._register(this.channel.listen('onDidUninstallExtension')(e => this.fireEvent(this._onDidUninstallExtension, { ...e, profileLocation: URI.revive(e.profileLocation) }))); this._register(this.channel.listen('onDidUpdateExtensionMetadata')(e => this._onDidUpdateExtensionMetadata.fire(transformIncomingExtension(e, null)))); } + protected fireEvent(event: Emitter, data: InstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: InstallExtensionResult[]): void; + protected fireEvent(event: Emitter, data: UninstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: DidUninstallExtensionEvent): void; + protected fireEvent(event: Emitter, data: ExtensionEventResult): void; + protected fireEvent(event: Emitter, data: ExtensionEventResult[]): void; + protected fireEvent(event: Emitter, data: E): void { + event.fire(data); + } + private isUriComponents(thing: unknown): thing is UriComponents { if (!thing) { return false; @@ -172,15 +255,15 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt .then(extensions => extensions.map(extension => transformIncomingExtension(extension, null))); } - getMetadata(local: ILocalExtension, extensionsProfileResource?: URI): Promise { - return Promise.resolve(this.channel.call('getMetadata', [local, extensionsProfileResource])); - } - updateMetadata(local: ILocalExtension, metadata: Partial, extensionsProfileResource?: URI): Promise { return Promise.resolve(this.channel.call('updateMetadata', [local, metadata, extensionsProfileResource])) .then(extension => transformIncomingExtension(extension, null)); } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return this.channel.call('copyExtensions', [fromProfileLocation, toProfileLocation]); + } + getExtensionsControlManifest(): Promise { return Promise.resolve(this.channel.call('getExtensionsControlManifest')); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4147a0ccdae..e38e0bf0b2a 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -169,10 +169,6 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return local; } - getMetadata(extension: ILocalExtension, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { - return this.extensionsScanner.scanMetadata(extension, profileLocation); - } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfilesService.defaultProfile.extensionsResource): Promise { this.logService.trace('ExtensionManagementService#updateMetadata', local.identifier.id); if (metadata.isPreReleaseVersion) { @@ -209,6 +205,10 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return this.installFromGallery(galleryExtension); } + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + return this.extensionsScanner.copyExtensions(fromProfileLocation, toProfileLocation); + } + markAsUninstalled(...extensions: IExtension[]): Promise { return this.extensionsScanner.setUninstalled(...extensions); } @@ -530,6 +530,14 @@ export class ExtensionsScanner extends Disposable { await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]); } + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + const fromExtensions = await this.scanExtensions(ExtensionType.User, fromProfileLocation); + const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(fromExtensions + .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ + .map(async e => ([e, await this.scanMetadata(e, fromProfileLocation)]))); + await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, toProfileLocation); + } + private async withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary) => void): Promise> { return this.uninstalledFileLimiter.queue(async () => { let raw: string | undefined; diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts index f850e0d5216..d83efeb9408 100644 --- a/src/vs/platform/remote/common/remoteAgentEnvironment.ts +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -6,6 +6,7 @@ import * as performance from 'vs/base/common/performance'; import { OperatingSystem } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IRemoteAgentEnvironment { pid: number; @@ -22,6 +23,10 @@ export interface IRemoteAgentEnvironment { arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; + profiles: { + all: IUserDataProfile[]; + home: URI; + }; } export interface RemoteAgentConnectionContext { diff --git a/src/vs/platform/remote/common/remoteExtensionsScanner.ts b/src/vs/platform/remote/common/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..792c0352bb7 --- /dev/null +++ b/src/vs/platform/remote/common/remoteExtensionsScanner.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IRemoteExtensionsScannerService = createDecorator('IRemoteExtensionsScannerService'); + +export const RemoteExtensionsScannerChannelName = 'remoteExtensionsScanner'; + +export interface IRemoteExtensionsScannerService { + readonly _serviceBrand: undefined; + + whenExtensionsReady(): Promise; + scanExtensions(): Promise; + scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise; +} diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 46fca623948..d8876d9e5ad 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -246,7 +246,10 @@ export class SharedProcess extends Disposable implements ISharedProcess { windowId: this.window.id, appRoot: this.environmentMainService.appRoot, codeCachePath: this.environmentMainService.codeCachePath, - profiles: this.userDataProfilesService.profiles, + profiles: { + home: this.userDataProfilesService.profilesHome, + all: this.userDataProfilesService.profiles, + }, userEnv: this.userEnv, args: this.environmentMainService.args, logLevel: this.loggerMainService.getLogLevel(), diff --git a/src/vs/platform/sharedProcess/node/sharedProcess.ts b/src/vs/platform/sharedProcess/node/sharedProcess.ts index 3b568b3b57f..d01f16c6a96 100644 --- a/src/vs/platform/sharedProcess/node/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/node/sharedProcess.ts @@ -9,7 +9,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ILoggerResource, LogLevel } from 'vs/platform/log/common/log'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { PolicyDefinition, PolicyValue } from 'vs/platform/policy/common/policy'; -import { UriDto } from 'vs/base/common/uri'; +import { UriComponents, UriDto } from 'vs/base/common/uri'; export interface ISharedProcess { @@ -29,7 +29,10 @@ export interface ISharedProcessConfiguration extends ISandboxConfiguration { readonly loggers: UriDto[]; - readonly profiles: readonly UriDto[]; + readonly profiles: { + readonly home: UriComponents; + readonly all: readonly UriDto[]; + }; readonly policiesData?: IStringDictionary<{ definition: PolicyDefinition; value: PolicyValue }>; } diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts similarity index 66% rename from src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts rename to src/vs/platform/userDataProfile/common/userDataProfileIpc.ts index 60767bf59ef..ceaa5ec8fc8 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfileIpc.ts @@ -5,22 +5,59 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { URI, UriDto } from 'vs/base/common/uri'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { IURITransformer, transformIncomingURIs, transformOutgoingURIs } from 'vs/base/common/uriIpc'; -export class UserDataProfilesNativeService extends Disposable implements IUserDataProfilesService { +export class RemoteUserDataProfilesServiceChannel implements IServerChannel { + + constructor( + private readonly service: IUserDataProfilesService, + private readonly getUriTransformer: (requestContext: any) => IURITransformer + ) { } + + listen(context: any, event: string): Event { + const uriTransformer = this.getUriTransformer(context); + switch (event) { + case 'onDidChangeProfiles': return Event.map(this.service.onDidChangeProfiles, e => { + return { + all: e.all.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + added: e.added.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + removed: e.removed.map(p => transformOutgoingURIs({ ...p }, uriTransformer)), + updated: e.updated.map(p => transformOutgoingURIs({ ...p }, uriTransformer)) + }; + }); + } + throw new Error(`Invalid listen ${event}`); + } + + async call(context: any, command: string, args?: any): Promise { + const uriTransformer = this.getUriTransformer(context); + switch (command) { + case 'createProfile': { + const profile = await this.service.createProfile(args[0], args[1], args[2]); + return transformOutgoingURIs({ ...profile }, uriTransformer); + } + case 'updateProfile': { + let profile = reviveProfile(transformIncomingURIs(args[0], uriTransformer), this.service.profilesHome.scheme); + profile = await this.service.updateProfile(profile, args[1]); + return transformOutgoingURIs({ ...profile }, uriTransformer); + } + case 'removeProfile': { + const profile = reviveProfile(transformIncomingURIs(args[0], uriTransformer), this.service.profilesHome.scheme); + return this.service.removeProfile(profile); + } + } + throw new Error(`Invalid call ${command}`); + } +} + +export class UserDataProfilesService extends Disposable implements IUserDataProfilesService { readonly _serviceBrand: undefined; - private readonly channel: IChannel; - - readonly profilesHome: URI; - get defaultProfile(): IUserDataProfile { return this.profiles[0]; } private _profiles: IUserDataProfile[] = []; get profiles(): IUserDataProfile[] { return this._profiles; } @@ -34,12 +71,10 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa constructor( profiles: readonly UriDto[], - @IMainProcessService mainProcessService: IMainProcessService, - @IEnvironmentService environmentService: IEnvironmentService, + readonly profilesHome: URI, + private readonly channel: IChannel, ) { super(); - this.channel = mainProcessService.getChannel('userDataProfiles'); - this.profilesHome = joinPath(environmentService.userRoamingDataHome, 'profiles'); this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme)); this._register(this.channel.listen('onDidChangeProfiles')(e => { const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme)); @@ -100,4 +135,3 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa } } - diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index ccb87376d87..11b1a3d7b0e 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -285,6 +285,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native backupPath?: string; profiles: { + home: UriComponents; all: readonly UriDto[]; profile: UriDto; }; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 88cc0db453f..4af29bb8635 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1087,7 +1087,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { configuration.continueOn = this.environmentMainService.continueOn; configuration.profiles = { all: this.userDataProfilesService.profiles, - profile: this.profile || this.userDataProfilesService.defaultProfile + profile: this.profile || this.userDataProfilesService.defaultProfile, + home: this.userDataProfilesService.profilesHome }; configuration.logLevel = this.loggerMainService.getLogLevel(); configuration.loggers = { diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b2282599855..a96d9456915 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1348,6 +1348,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic backupPath: options.emptyWindowBackupInfo ? join(this.environmentMainService.backupHome, options.emptyWindowBackupInfo.backupFolder) : undefined, profiles: { + home: this.userDataProfilesMainService.profilesHome, all: this.userDataProfilesMainService.profiles, // Set to default profile first and resolve and update the profile // only after the workspace-backup is registered. diff --git a/src/vs/server/node/remoteAgentEnvironmentImpl.ts b/src/vs/server/node/remoteAgentEnvironmentImpl.ts index 19d01ff49c8..a991de0deae 100644 --- a/src/vs/server/node/remoteAgentEnvironmentImpl.ts +++ b/src/vs/server/node/remoteAgentEnvironmentImpl.ts @@ -8,63 +8,29 @@ import * as platform from 'vs/base/common/platform'; import * as performance from 'vs/base/common/performance'; import { URI } from 'vs/base/common/uri'; import { createURITransformer } from 'vs/workbench/api/node/uriTransformer'; -import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IScanExtensionsArguments, IScanSingleExtensionArguments, IGetExtensionHostExitInfoArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; -import { Schemas } from 'vs/base/common/network'; +import { IRemoteAgentEnvironmentDTO, IGetEnvironmentDataArguments, IGetExtensionHostExitInfoArguments } from 'vs/workbench/services/remote/common/remoteAgentEnvironmentChannel'; import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { transformOutgoingURIs } from 'vs/base/common/uriIpc'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ContextKeyExpr, ContextKeyDefinedExpr, ContextKeyNotExpr, ContextKeyEqualsExpr, ContextKeyNotEqualsExpr, ContextKeyRegexExpr, IContextKeyExprMapper, ContextKeyExpression, ContextKeyInExpr, ContextKeyGreaterExpr, ContextKeyGreaterEqualsExpr, ContextKeySmallerExpr, ContextKeySmallerEqualsExpr, ContextKeyNotInExpr } from 'vs/platform/contextkey/common/contextkey'; import { listProcesses } from 'vs/base/node/ps'; import { getMachineInfo, collectWorkspaceStats } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { basename, isAbsolute, join, resolve } from 'vs/base/common/path'; +import { basename, join } from 'vs/base/common/path'; import { ProcessItem } from 'vs/base/common/processes'; -import { InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { cwd } from 'vs/base/common/process'; import { ServerConnectionToken, ServerConnectionTokenType } from 'vs/server/node/serverConnectionToken'; import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService'; -import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService'; -import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; export class RemoteAgentEnvironmentChannel implements IServerChannel { private static _namePool = 1; - private readonly whenExtensionsReady: Promise; - constructor( private readonly _connectionToken: ServerConnectionToken, private readonly _environmentService: IServerEnvironmentService, private readonly _userDataProfilesService: IUserDataProfilesService, - extensionManagementCLI: ExtensionManagementCLI, - private readonly _logService: ILogService, private readonly _extensionHostStatusService: IExtensionHostStatusService, - private readonly _extensionsScannerService: IExtensionsScannerService, ) { - if (_environmentService.args['install-builtin-extension']) { - const installOptions: InstallOptions = { isMachineScoped: !!_environmentService.args['do-not-sync'], installPreReleaseVersion: !!_environmentService.args['pre-release'] }; - performance.mark('code/server/willInstallBuiltinExtensions'); - this.whenExtensionsReady = extensionManagementCLI.installExtensions([], _environmentService.args['install-builtin-extension'], installOptions, !!_environmentService.args['force']) - .then(() => performance.mark('code/server/didInstallBuiltinExtensions'), error => { - _logService.error(error); - }); - } else { - this.whenExtensionsReady = Promise.resolve(); - } - - const extensionsToInstall = _environmentService.args['install-extension']; - if (extensionsToInstall) { - const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); - this.whenExtensionsReady - .then(() => extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!_environmentService.args['do-not-sync'], installPreReleaseVersion: !!_environmentService.args['pre-release'] }, !!_environmentService.args['force'])) - .then(null, error => { - _logService.error(error); - }); - } } async call(_: any, command: string, arg?: any): Promise { @@ -74,7 +40,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { const args = arg; const uriTransformer = createURITransformer(args.remoteAuthority); - let environmentData = await this._getEnvironmentData(); + let environmentData = await this._getEnvironmentData(args.profile); environmentData = transformOutgoingURIs(environmentData, uriTransformer); return environmentData; @@ -85,59 +51,6 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { return this._extensionHostStatusService.getExitInfo(args.reconnectionToken); } - case 'whenExtensionsReady': { - await this.whenExtensionsReady; - return; - } - - case 'scanExtensions': { - await this.whenExtensionsReady; - performance.mark('code/server/willScanExtensions'); - - const args = arg; - const language = args.language; - this._logService.trace(`Scanning extensions using UI language: ${language}`); - const uriTransformer = createURITransformer(args.remoteAuthority); - - const extensionDevelopmentLocations = args.extensionDevelopmentPath && args.extensionDevelopmentPath.map(url => URI.revive(uriTransformer.transformIncoming(url))); - const extensionDevelopmentPath = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined; - - let extensions = await this._scanExtensions(language, extensionDevelopmentPath); - extensions = transformOutgoingURIs(extensions, uriTransformer); - - this._logService.trace('Scanned Extensions', extensions); - RemoteAgentEnvironmentChannel._massageWhenConditions(extensions); - - performance.mark('code/server/didScanExtensions'); - return extensions; - } - - case 'scanSingleExtension': { - await this.whenExtensionsReady; - const args = arg; - const language = args.language; - const isBuiltin = args.isBuiltin; - const uriTransformer = createURITransformer(args.remoteAuthority); - const extensionLocation = URI.revive(uriTransformer.transformIncoming(args.extensionLocation)); - const extensionPath = extensionLocation.scheme === Schemas.file ? extensionLocation.fsPath : null; - - if (!extensionPath) { - return null; - } - - let extension = await this._scanSingleExtension(extensionPath, isBuiltin, language); - - if (!extension) { - return null; - } - - extension = transformOutgoingURIs(extension, uriTransformer); - - RemoteAgentEnvironmentChannel._massageWhenConditions([extension]); - - return extension; - } - case 'getDiagnosticInfo': { const options = arg; const diagnosticInfo: IDiagnosticInfo = { @@ -178,120 +91,10 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { throw new Error('Not supported'); } - private static _massageWhenConditions(extensions: IExtensionDescription[]): void { - // Massage "when" conditions which mention `resourceScheme` - - interface WhenUser { when?: string } - - interface LocWhenUser { [loc: string]: WhenUser[] } - - const _mapResourceSchemeValue = (value: string, isRegex: boolean): string => { - // console.log(`_mapResourceSchemeValue: ${value}, ${isRegex}`); - return value.replace(/file/g, 'vscode-remote'); - }; - - const _mapResourceRegExpValue = (value: RegExp): RegExp => { - let flags = ''; - flags += value.global ? 'g' : ''; - flags += value.ignoreCase ? 'i' : ''; - flags += value.multiline ? 'm' : ''; - return new RegExp(_mapResourceSchemeValue(value.source, true), flags); - }; - - const _exprKeyMapper = new class implements IContextKeyExprMapper { - mapDefined(key: string): ContextKeyExpression { - return ContextKeyDefinedExpr.create(key); - } - mapNot(key: string): ContextKeyExpression { - return ContextKeyNotExpr.create(key); - } - mapEquals(key: string, value: any): ContextKeyExpression { - if (key === 'resourceScheme' && typeof value === 'string') { - return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); - } else { - return ContextKeyEqualsExpr.create(key, value); - } - } - mapNotEquals(key: string, value: any): ContextKeyExpression { - if (key === 'resourceScheme' && typeof value === 'string') { - return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); - } else { - return ContextKeyNotEqualsExpr.create(key, value); - } - } - mapGreater(key: string, value: any): ContextKeyExpression { - return ContextKeyGreaterExpr.create(key, value); - } - mapGreaterEquals(key: string, value: any): ContextKeyExpression { - return ContextKeyGreaterEqualsExpr.create(key, value); - } - mapSmaller(key: string, value: any): ContextKeyExpression { - return ContextKeySmallerExpr.create(key, value); - } - mapSmallerEquals(key: string, value: any): ContextKeyExpression { - return ContextKeySmallerEqualsExpr.create(key, value); - } - mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr { - if (key === 'resourceScheme' && regexp) { - return ContextKeyRegexExpr.create(key, _mapResourceRegExpValue(regexp)); - } else { - return ContextKeyRegexExpr.create(key, regexp); - } - } - mapIn(key: string, valueKey: string): ContextKeyInExpr { - return ContextKeyInExpr.create(key, valueKey); - } - mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr { - return ContextKeyNotInExpr.create(key, valueKey); - } - }; - - const _massageWhenUser = (element: WhenUser) => { - if (!element || !element.when || !/resourceScheme/.test(element.when)) { - return; - } - - const expr = ContextKeyExpr.deserialize(element.when); - if (!expr) { - return; - } - - const massaged = expr.map(_exprKeyMapper); - element.when = massaged.serialize(); - }; - - const _massageWhenUserArr = (elements: WhenUser[] | WhenUser) => { - if (Array.isArray(elements)) { - for (const element of elements) { - _massageWhenUser(element); - } - } else { - _massageWhenUser(elements); - } - }; - - const _massageLocWhenUser = (target: LocWhenUser) => { - for (const loc in target) { - _massageWhenUserArr(target[loc]); - } - }; - - extensions.forEach((extension) => { - if (extension.contributes) { - if (extension.contributes.menus) { - _massageLocWhenUser(extension.contributes.menus); - } - if (extension.contributes.keybindings) { - _massageWhenUserArr(extension.contributes.keybindings); - } - if (extension.contributes.views) { - _massageLocWhenUser(extension.contributes.views); - } - } - }); - } - - private async _getEnvironmentData(): Promise { + private async _getEnvironmentData(profile?: string): Promise { + if (profile && !this._userDataProfilesService.profiles.some(p => p.id === profile)) { + await this._userDataProfilesService.createProfile(profile, profile); + } return { pid: process.pid, connectionToken: (this._connectionToken.type !== ServerConnectionTokenType.None ? this._connectionToken.value : ''), @@ -306,46 +109,12 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel { os: platform.OS, arch: process.arch, marks: performance.getMarks(), - useHostProxy: !!this._environmentService.args['use-host-proxy'] + useHostProxy: !!this._environmentService.args['use-host-proxy'], + profiles: { + home: this._userDataProfilesService.profilesHome, + all: [...this._userDataProfilesService.profiles].map(profile => ({ ...profile })) + } }; } - private async _scanExtensions(language: string, extensionDevelopmentPath?: string[]): Promise { - // Ensure that the language packs are available - - const [builtinExtensions, installedExtensions, developedExtensions] = await Promise.all([ - this._scanBuiltinExtensions(language), - this._scanInstalledExtensions(language), - this._scanDevelopedExtensions(language, extensionDevelopmentPath) - ]); - - return dedupExtensions(builtinExtensions, installedExtensions, developedExtensions, this._logService); - } - - private async _scanDevelopedExtensions(language: string, extensionDevelopmentPaths?: string[]): Promise { - if (extensionDevelopmentPaths) { - return (await Promise.all(extensionDevelopmentPaths.map(extensionDevelopmentPath => this._extensionsScannerService.scanOneOrMultipleExtensions(URI.file(resolve(extensionDevelopmentPath)), ExtensionType.User, { language })))) - .flat() - .map(e => toExtensionDescription(e, true)); - } - return []; - } - - private async _scanBuiltinExtensions(language: string): Promise { - const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language, useCache: true }); - return scannedExtensions.map(e => toExtensionDescription(e, false)); - } - - private async _scanInstalledExtensions(language: string): Promise { - const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ profileLocation: this._userDataProfilesService.defaultProfile.extensionsResource, language, useCache: true }); - return scannedExtensions.map(e => toExtensionDescription(e, false)); - } - - private async _scanSingleExtension(extensionPath: string, isBuiltin: boolean, language: string): Promise { - const extensionLocation = URI.file(resolve(extensionPath)); - const type = isBuiltin ? ExtensionType.System : ExtensionType.User; - const scannedExtension = await this._extensionsScannerService.scanExistingExtension(extensionLocation, type, { language }); - return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; - } - } diff --git a/src/vs/server/node/remoteExtensionsScanner.ts b/src/vs/server/node/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..d4b130ba630 --- /dev/null +++ b/src/vs/server/node/remoteExtensionsScanner.ts @@ -0,0 +1,282 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isAbsolute, join, resolve } from 'vs/base/common/path'; +import * as platform from 'vs/base/common/platform'; +import { cwd } from 'vs/base/common/process'; +import { URI } from 'vs/base/common/uri'; +import * as performance from 'vs/base/common/performance'; +import { Event } from 'vs/base/common/event'; +import { IURITransformer, transformOutgoingURIs } from 'vs/base/common/uriIpc'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ContextKeyDefinedExpr, ContextKeyEqualsExpr, ContextKeyExpr, ContextKeyExpression, ContextKeyGreaterEqualsExpr, ContextKeyGreaterExpr, ContextKeyInExpr, ContextKeyNotEqualsExpr, ContextKeyNotExpr, ContextKeyNotInExpr, ContextKeyRegexExpr, ContextKeySmallerEqualsExpr, ContextKeySmallerExpr, IContextKeyExprMapper } from 'vs/platform/contextkey/common/contextkey'; +import { InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; +import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/extensionManagement/common/extensionsScannerService'; +import { ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IServerEnvironmentService } from 'vs/server/node/serverEnvironmentService'; +import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { Schemas } from 'vs/base/common/network'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; + +export class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + + readonly _serviceBrand: undefined; + + private readonly _whenExtensionsReady: Promise; + + constructor( + extensionManagementCLI: ExtensionManagementCLI, + environmentService: IServerEnvironmentService, + private readonly _userDataProfilesService: IUserDataProfilesService, + private readonly _extensionsScannerService: IExtensionsScannerService, + private readonly _logService: ILogService, + ) { + if (environmentService.args['install-builtin-extension']) { + const installOptions: InstallOptions = { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }; + performance.mark('code/server/willInstallBuiltinExtensions'); + this._whenExtensionsReady = extensionManagementCLI.installExtensions([], environmentService.args['install-builtin-extension'], installOptions, !!environmentService.args['force']) + .then(() => performance.mark('code/server/didInstallBuiltinExtensions'), error => { + _logService.error(error); + }); + } else { + this._whenExtensionsReady = Promise.resolve(); + } + + const extensionsToInstall = environmentService.args['install-extension']; + if (extensionsToInstall) { + const idsOrVSIX = extensionsToInstall.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); + this._whenExtensionsReady + .then(() => extensionManagementCLI.installExtensions(idsOrVSIX, [], { isMachineScoped: !!environmentService.args['do-not-sync'], installPreReleaseVersion: !!environmentService.args['pre-release'] }, !!environmentService.args['force'])) + .then(null, error => { + _logService.error(error); + }); + } + } + + whenExtensionsReady(): Promise { + return this._whenExtensionsReady; + } + + async scanExtensions(language?: string, profileLocation?: URI, extensionDevelopmentLocations?: URI[]): Promise { + await this.whenExtensionsReady(); + + performance.mark('code/server/willScanExtensions'); + + this._logService.trace(`Scanning extensions using UI language: ${language}`); + + const extensionDevelopmentPaths = extensionDevelopmentLocations ? extensionDevelopmentLocations.filter(url => url.scheme === Schemas.file).map(url => url.fsPath) : undefined; + profileLocation = profileLocation ?? this._userDataProfilesService.defaultProfile.extensionsResource; + + const extensions = await this._scanExtensions(profileLocation, language ?? platform.language, extensionDevelopmentPaths); + + this._logService.trace('Scanned Extensions', extensions); + this._massageWhenConditions(extensions); + + performance.mark('code/server/didScanExtensions'); + return extensions; + } + + async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean, language?: string): Promise { + await this.whenExtensionsReady(); + + const extensionPath = extensionLocation.scheme === Schemas.file ? extensionLocation.fsPath : null; + + if (!extensionPath) { + return null; + } + + const extension = await this._scanSingleExtension(extensionPath, isBuiltin, language ?? platform.language); + + if (!extension) { + return null; + } + + this._massageWhenConditions([extension]); + + return extension; + } + + private async _scanExtensions(profileLocation: URI, language: string, extensionDevelopmentPath?: string[]): Promise { + // Ensure that the language packs are available + + const [builtinExtensions, installedExtensions, developedExtensions] = await Promise.all([ + this._scanBuiltinExtensions(language), + this._scanInstalledExtensions(profileLocation, language), + this._scanDevelopedExtensions(language, extensionDevelopmentPath) + ]); + + return dedupExtensions(builtinExtensions, installedExtensions, developedExtensions, this._logService); + } + + private async _scanDevelopedExtensions(language: string, extensionDevelopmentPaths?: string[]): Promise { + if (extensionDevelopmentPaths) { + return (await Promise.all(extensionDevelopmentPaths.map(extensionDevelopmentPath => this._extensionsScannerService.scanOneOrMultipleExtensions(URI.file(resolve(extensionDevelopmentPath)), ExtensionType.User, { language })))) + .flat() + .map(e => toExtensionDescription(e, true)); + } + return []; + } + + private async _scanBuiltinExtensions(language: string): Promise { + const scannedExtensions = await this._extensionsScannerService.scanSystemExtensions({ language, useCache: true }); + return scannedExtensions.map(e => toExtensionDescription(e, false)); + } + + private async _scanInstalledExtensions(profileLocation: URI, language: string): Promise { + const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ profileLocation, language, useCache: true }); + return scannedExtensions.map(e => toExtensionDescription(e, false)); + } + + private async _scanSingleExtension(extensionPath: string, isBuiltin: boolean, language: string): Promise { + const extensionLocation = URI.file(resolve(extensionPath)); + const type = isBuiltin ? ExtensionType.System : ExtensionType.User; + const scannedExtension = await this._extensionsScannerService.scanExistingExtension(extensionLocation, type, { language }); + return scannedExtension ? toExtensionDescription(scannedExtension, false) : null; + } + + private _massageWhenConditions(extensions: IExtensionDescription[]): void { + // Massage "when" conditions which mention `resourceScheme` + + interface WhenUser { when?: string } + + interface LocWhenUser { [loc: string]: WhenUser[] } + + const _mapResourceSchemeValue = (value: string, isRegex: boolean): string => { + // console.log(`_mapResourceSchemeValue: ${value}, ${isRegex}`); + return value.replace(/file/g, 'vscode-remote'); + }; + + const _mapResourceRegExpValue = (value: RegExp): RegExp => { + let flags = ''; + flags += value.global ? 'g' : ''; + flags += value.ignoreCase ? 'i' : ''; + flags += value.multiline ? 'm' : ''; + return new RegExp(_mapResourceSchemeValue(value.source, true), flags); + }; + + const _exprKeyMapper = new class implements IContextKeyExprMapper { + mapDefined(key: string): ContextKeyExpression { + return ContextKeyDefinedExpr.create(key); + } + mapNot(key: string): ContextKeyExpression { + return ContextKeyNotExpr.create(key); + } + mapEquals(key: string, value: any): ContextKeyExpression { + if (key === 'resourceScheme' && typeof value === 'string') { + return ContextKeyEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); + } else { + return ContextKeyEqualsExpr.create(key, value); + } + } + mapNotEquals(key: string, value: any): ContextKeyExpression { + if (key === 'resourceScheme' && typeof value === 'string') { + return ContextKeyNotEqualsExpr.create(key, _mapResourceSchemeValue(value, false)); + } else { + return ContextKeyNotEqualsExpr.create(key, value); + } + } + mapGreater(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterExpr.create(key, value); + } + mapGreaterEquals(key: string, value: any): ContextKeyExpression { + return ContextKeyGreaterEqualsExpr.create(key, value); + } + mapSmaller(key: string, value: any): ContextKeyExpression { + return ContextKeySmallerExpr.create(key, value); + } + mapSmallerEquals(key: string, value: any): ContextKeyExpression { + return ContextKeySmallerEqualsExpr.create(key, value); + } + mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr { + if (key === 'resourceScheme' && regexp) { + return ContextKeyRegexExpr.create(key, _mapResourceRegExpValue(regexp)); + } else { + return ContextKeyRegexExpr.create(key, regexp); + } + } + mapIn(key: string, valueKey: string): ContextKeyInExpr { + return ContextKeyInExpr.create(key, valueKey); + } + mapNotIn(key: string, valueKey: string): ContextKeyNotInExpr { + return ContextKeyNotInExpr.create(key, valueKey); + } + }; + + const _massageWhenUser = (element: WhenUser) => { + if (!element || !element.when || !/resourceScheme/.test(element.when)) { + return; + } + + const expr = ContextKeyExpr.deserialize(element.when); + if (!expr) { + return; + } + + const massaged = expr.map(_exprKeyMapper); + element.when = massaged.serialize(); + }; + + const _massageWhenUserArr = (elements: WhenUser[] | WhenUser) => { + if (Array.isArray(elements)) { + for (const element of elements) { + _massageWhenUser(element); + } + } else { + _massageWhenUser(elements); + } + }; + + const _massageLocWhenUser = (target: LocWhenUser) => { + for (const loc in target) { + _massageWhenUserArr(target[loc]); + } + }; + + extensions.forEach((extension) => { + if (extension.contributes) { + if (extension.contributes.menus) { + _massageLocWhenUser(extension.contributes.menus); + } + if (extension.contributes.keybindings) { + _massageWhenUserArr(extension.contributes.keybindings); + } + if (extension.contributes.views) { + _massageLocWhenUser(extension.contributes.views); + } + } + }); + } +} + +export class RemoteExtensionsScannerChannel implements IServerChannel { + + constructor(private service: RemoteExtensionsScannerService, private getUriTransformer: (requestContext: any) => IURITransformer) { } + + listen(context: any, event: string): Event { + throw new Error('Invalid listen'); + } + + async call(context: any, command: string, args?: any): Promise { + const uriTransformer = this.getUriTransformer(context); + switch (command) { + case 'whenExtensionsReady': return this.service.whenExtensionsReady(); + case 'scanExtensions': { + const language = args[0]; + const profileLocation = args[1] ? URI.revive(uriTransformer.transformIncoming(args[1])) : undefined; + const extensionDevelopmentPath = Array.isArray(args[2]) ? args[2].map(u => URI.revive(uriTransformer.transformIncoming(u))) : undefined; + const extensions = await this.service.scanExtensions(language, profileLocation, extensionDevelopmentPath); + return extensions.map(extension => transformOutgoingURIs(extension, uriTransformer)); + } + case 'scanSingleExtension': { + const extension = await this.service.scanSingleExtension(URI.revive(uriTransformer.transformIncoming(args[0])), args[1], args[2]); + return extension ? transformOutgoingURIs(extension, uriTransformer) : null; + } + } + throw new Error('Invalid call'); + } +} diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index c51c166cf51..edb7b0067d6 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -79,6 +79,9 @@ import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement import { LogService } from 'vs/platform/log/common/logService'; import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { localize } from 'vs/nls'; +import { RemoteExtensionsScannerChannel, RemoteExtensionsScannerService } from 'vs/server/node/remoteExtensionsScanner'; +import { RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import { RemoteUserDataProfilesServiceChannel } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; const eventPrefix = 'monacoworkbench'; @@ -130,6 +133,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken // User Data Profiles const userDataProfilesService = new ServerUserDataProfilesService(uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); + socketServer.registerChannel('userDataProfiles', new RemoteUserDataProfilesServiceChannel(userDataProfilesService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); // Initialize const [, , machineId] = await Promise.all([ @@ -205,7 +209,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken instantiationService.invokeFunction(accessor => { const extensionManagementService = accessor.get(INativeServerExtensionManagementService); const extensionsScannerService = accessor.get(IExtensionsScannerService); - const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, instantiationService.createInstance(ExtensionManagementCLI), logService, extensionHostStatusService, extensionsScannerService); + const remoteExtensionEnvironmentChannel = new RemoteAgentEnvironmentChannel(connectionToken, environmentService, userDataProfilesService, extensionHostStatusService); socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel); const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender); @@ -213,6 +217,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken socketServer.registerChannel(REMOTE_TERMINAL_CHANNEL_NAME, new RemoteTerminalChannel(environmentService, logService, ptyService, productService, extensionManagementService, configurationService)); + const remoteExtensionsScanner = new RemoteExtensionsScannerService(instantiationService.createInstance(ExtensionManagementCLI), environmentService, userDataProfilesService, extensionsScannerService, logService); + socketServer.registerChannel(RemoteExtensionsScannerChannelName, new RemoteExtensionsScannerChannel(remoteExtensionsScanner, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority))); + const remoteFileSystemChannel = new RemoteAgentFileSystemProviderChannel(logService, environmentService); socketServer.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, remoteFileSystemChannel); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index e5c6c33e5dd..9ff45090e6a 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -268,10 +268,6 @@ export class BrowserMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, environmentService, productService, remoteAuthorityResolverService, signService, logService)); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - // Files const fileService = this._register(new FileService(logService)); serviceCollection.set(IWorkbenchFileService, fileService); @@ -280,8 +276,6 @@ export class BrowserMain extends Disposable { const loggerService = new FileLoggerService(logLevel, fileService); serviceCollection.set(ILoggerService, loggerService); - await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); - // URI Identity const uriIdentityService = new UriIdentityService(fileService); serviceCollection.set(IUriIdentityService, uriIdentityService); @@ -289,15 +283,17 @@ export class BrowserMain extends Disposable { // User Data Profiles const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - if (environmentService.remoteAuthority) { - // Always Disabled in web with remote connection - userDataProfilesService.setEnablement(false); - } const currentProfile = userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile; const userDataProfileService = new UserDataProfileService(currentProfile, userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Remote Agent + const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); + // Long running services (workspace, config, storage) const [configurationService, storageService] = await Promise.all([ this.createWorkspaceService(workspace, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index d01d405bb5e..ea8d8b0a613 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -49,7 +49,7 @@ import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-sandbox/diskFileSystemProvider'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; @@ -217,10 +217,6 @@ export class DesktopMain extends Disposable { const signService = ProxyChannel.toService(mainProcessService.getChannel('sign')); serviceCollection.set(ISignService, signService); - // Remote Agent - const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService)); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - // Files const fileService = this._register(new FileService(logService)); serviceCollection.set(IWorkbenchFileService, fileService); @@ -229,9 +225,6 @@ export class DesktopMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(mainProcessService, utilityProcessWorkerWorkbenchService, logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // Remote Files - this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); - // User Data Provider fileService.registerProvider(Schemas.vscodeUserData, this._register(new FileUserDataProvider(Schemas.file, diskFileSystemProvider, Schemas.vscodeUserData, logService))); @@ -240,11 +233,18 @@ export class DesktopMain extends Disposable { serviceCollection.set(IUriIdentityService, uriIdentityService); // User Data Profiles - const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService); + const userDataProfilesService = new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home), mainProcessService.getChannel('userDataProfiles')); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.profile, userDataProfilesService.profilesHome.scheme), userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Remote Agent + const remoteAgentService = this._register(new RemoteAgentService(userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); + serviceCollection.set(IRemoteAgentService, remoteAgentService); + + // Remote Files + this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // // NOTE: Please do NOT register services here. Use `registerSingleton()` diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index e55a03e2baa..d95c45d5285 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -88,7 +88,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -130,7 +131,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -152,7 +154,8 @@ suite('WorkspaceContextService - Folder', () => { fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const uriIdentityService = new UriIdentityService(fileService); const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, new RemoteAgentService(null, userDataProfileService, environmentService, TestProductService, new RemoteAuthorityResolverService(undefined, undefined, TestProductService, logService), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (testObject).initialize(convertToWorkspacePayload(folder)); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 10ec4f9a09d..2c15566fffc 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -14,10 +14,6 @@ export type DidChangeProfileEvent = { readonly added: ILocalExtension[]; readonl export const IProfileAwareExtensionManagementService = refineServiceDecorator(IExtensionManagementService); export interface IProfileAwareExtensionManagementService extends IExtensionManagementService { - readonly onProfileAwareInstallExtension: Event; - readonly onProfileAwareDidInstallExtensions: Event; - readonly onProfileAwareUninstallExtension: Event; - readonly onProfileAwareDidUninstallExtension: Event; readonly onDidChangeProfile: Event; } @@ -58,10 +54,6 @@ export interface IWorkbenchExtensionManagementService extends IProfileAwareExten onDidInstallExtensions: Event; onUninstallExtension: Event; onDidUninstallExtension: Event; - onProfileAwareInstallExtension: Event; - onProfileAwareDidInstallExtensions: Event; - onProfileAwareUninstallExtension: Event; - onProfileAwareDidUninstallExtension: Event; onDidChangeProfile: Event; installVSIX(location: URI, manifest: IExtensionManifest, installOptions?: InstallVSIXOptions): Promise; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts new file mode 100644 index 00000000000..9332af3584f --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementChannelClient.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { URI } from 'vs/base/common/uri'; +import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionManagementChannelClient as BaseExtensionManagementChannelClient, ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { Emitter } from 'vs/base/common/event'; +import { delta } from 'vs/base/common/arrays'; +import { compare } from 'vs/base/common/strings'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; + +export abstract class ProfileAwareExtensionManagementChannelClient extends BaseExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { + + private readonly _onDidChangeProfile = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); + readonly onDidChangeProfile = this._onDidChangeProfile.event; + + constructor(channel: IChannel, + protected readonly userDataProfileService: IUserDataProfileService, + protected readonly uriIdentityService: IUriIdentityService, + ) { + super(channel); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + } + + protected override fireEvent(event: Emitter, data: InstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: InstallExtensionResult[]): Promise; + protected override fireEvent(event: Emitter, data: UninstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: DidUninstallExtensionEvent): Promise; + protected override fireEvent(event: Emitter, data: ExtensionEventResult): Promise; + protected override fireEvent(event: Emitter, data: ExtensionEventResult[]): Promise; + protected override async fireEvent(arg0: any, arg1: any): Promise { + if (Array.isArray(arg1)) { + const event = arg0 as Emitter; + const data = arg1 as ExtensionEventResult[]; + const filtered = []; + for (const e of data) { + const result = this.filterEvent(e); + if (result instanceof Promise ? await result : result) { + filtered.push(e); + } + } + if (filtered.length) { + event.fire(filtered); + } + } else { + const event = arg0 as Emitter; + const data = arg1 as ExtensionEventResult; + const result = this.filterEvent(data); + if (result instanceof Promise ? await result : result) { + event.fire(data); + } + } + } + + override async install(vsix: URI, installOptions?: InstallVSIXOptions): Promise { + installOptions = { ...installOptions, profileLocation: await this.getProfileLocation(installOptions?.profileLocation) }; + return super.install(vsix, installOptions); + } + + override async installFromLocation(location: URI, profileLocation: URI): Promise { + return super.installFromLocation(location, await this.getProfileLocation(profileLocation)); + } + + override async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { + installOptions = { ...installOptions, profileLocation: await this.getProfileLocation(installOptions?.profileLocation) }; + return super.installFromGallery(extension, installOptions); + } + + override async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { + options = { ...options, profileLocation: await this.getProfileLocation(options?.profileLocation) }; + return super.uninstall(extension, options); + } + + override async getInstalled(type: ExtensionType | null = null, extensionsProfileResource?: URI): Promise { + return super.getInstalled(type, await this.getProfileLocation(extensionsProfileResource)); + } + + override async updateMetadata(local: ILocalExtension, metadata: Partial, extensionsProfileResource?: URI): Promise { + return super.updateMetadata(local, metadata, await this.getProfileLocation(extensionsProfileResource)); + } + + protected async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + const previousProfileLocation = await this.getProfileLocation(e.previous.extensionsResource); + const currentProfileLocation = await this.getProfileLocation(e.profile.extensionsResource); + + if (this.uriIdentityService.extUri.isEqual(previousProfileLocation, currentProfileLocation)) { + return; + } + + if (e.preserveData) { + await this.copyExtensions(previousProfileLocation, currentProfileLocation); + this._onDidChangeProfile.fire({ added: [], removed: [] }); + } else { + const oldExtensions = await this.getInstalled(ExtensionType.User, previousProfileLocation); + const newExtensions = await this.getInstalled(ExtensionType.User, currentProfileLocation); + const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); + this._onDidChangeProfile.fire({ added, removed }); + } + } + + protected getProfileLocation(profileLocation: URI): Promise; + protected getProfileLocation(profileLocation?: URI): Promise; + protected async getProfileLocation(profileLocation?: URI): Promise { + return profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource; + } + + protected abstract filterEvent(e: ExtensionEventResult): boolean | Promise; +} diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index abab8d56829..ca92c115f5c 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -42,10 +42,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench readonly onUninstallExtension: Event; readonly onDidUninstallExtension: Event; readonly onDidUpdateExtensionMetadata: Event; - readonly onProfileAwareInstallExtension: Event; - readonly onProfileAwareDidInstallExtensions: Event; - readonly onProfileAwareUninstallExtension: Event; - readonly onProfileAwareDidUninstallExtension: Event; readonly onDidChangeProfile: Event; protected readonly servers: IExtensionManagementServer[] = []; @@ -81,10 +77,6 @@ export class ExtensionManagementService extends Disposable implements IWorkbench this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidUpdateExtensionMetadata = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onDidUpdateExtensionMetadata); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareInstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareDidInstallExtensions = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(server.extensionManagementService.onProfileAwareDidInstallExtensions); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; - this.onProfileAwareDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onProfileAwareDidUninstallExtension, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; this.onDidChangeProfile = this._register(this.servers.reduce((emitter: EventMultiplexer, server) => { emitter.add(Event.map(server.extensionManagementService.onDidChangeProfile, e => ({ ...e, server }))); return emitter; }, new EventMultiplexer())).event; } @@ -529,17 +521,10 @@ export class ExtensionManagementService extends Disposable implements IWorkbench return this._targetPlatformPromise; } - async getMetadata(extension: ILocalExtension): Promise { - const server = this.getServer(extension); - if (!server) { - return undefined; - } - return server.extensionManagementService.getMetadata(extension); - } - async cleanUp(): Promise { await Promise.allSettled(this.servers.map(server => server.extensionManagementService.cleanUp())); } registerParticipant() { throw new Error('Not Supported'); } + copyExtensions(): Promise { throw new Error('Not Supported'); } } diff --git a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts index 0eb60966707..303967c7d88 100644 --- a/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/remoteExtensionManagementService.ts @@ -4,59 +4,62 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { ILocalExtension, IGalleryExtension, InstallOptions, InstallVSIXOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ExtensionEventResult } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -export class RemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - readonly onDidChangeProfile = Event.None; - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } +export class RemoteExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, - @IUserDataProfilesService private readonly userDataProfileService: IUserDataProfilesService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(channel); + super(channel, userDataProfileService, uriIdentityService); } - override getInstalled(type: ExtensionType | null = null, profileLocation?: URI): Promise { - this.validateProfileLocation({ profileLocation }); - return super.getInstalled(type); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - options = this.validateProfileLocation(options); - return super.uninstall(extension, options); - } - - override async install(vsix: URI, options?: InstallVSIXOptions): Promise { - options = this.validateProfileLocation(options); - return super.install(vsix, options); - } - - override async installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise { - options = this.validateProfileLocation(options); - return super.installFromGallery(extension, options); - } - - private validateProfileLocation(options?: T): T | undefined { - if (options?.profileLocation) { - if (!this.uriIdentityService.extUri.isEqual(options?.profileLocation, this.userDataProfileService.defaultProfile.extensionsResource)) { - throw new Error('This opertaion is not supported in remote scenario'); - } - options = { ...options, profileLocation: undefined }; + protected async filterEvent(e: ExtensionEventResult): Promise { + if (e.applicationScoped) { + return true; } - return options; + if (!e.profileLocation && this.userDataProfileService.currentProfile.isDefault) { + return true; + } + const currentRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(this.userDataProfileService.currentProfile); + if (this.uriIdentityService.extUri.isEqual(currentRemoteProfile.extensionsResource, e.profileLocation)) { + return true; + } + return false; } + protected override getProfileLocation(profileLocation: URI): Promise; + protected override getProfileLocation(profileLocation?: URI): Promise; + protected override async getProfileLocation(profileLocation?: URI): Promise { + if (!profileLocation && this.userDataProfileService.currentProfile.isDefault) { + return undefined; + } + profileLocation = await super.getProfileLocation(profileLocation); + let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, profileLocation)); + if (profile) { + profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile); + } else { + profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.extensionsResource, profileLocation)); + } + return profile?.extensionsResource; + } + + protected override async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { + const previousRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.previous); + const currentRemoteProfile = await this.remoteUserDataProfilesService.getRemoteProfile(e.profile); + if (previousRemoteProfile.id !== currentRemoteProfile.id) { + return super.whenProfileChanged(e.preserveData && currentRemoteProfile.isDefault ? { ...e, preserveData: false } : e); + } + } } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 0175a3edcfa..c00c0a582f8 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -112,11 +112,6 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return this.install(location, { profileLocation }); } - async getMetadata(extension: ILocalExtension, profileLocation?: URI): Promise { - const scannedExtension = await this.webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource); - return scannedExtension?.metadata; - } - async updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { // unset if false metadata.isMachineScoped = metadata.isMachineScoped || undefined; @@ -128,6 +123,10 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return updatedLocalExtension; } + override async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI): Promise { + await this.webExtensionsScannerService.copyExtensions(fromProfileLocation, toProfileLocation, e => !e.metadata?.isApplicationScoped); + } + protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise { const compatibleExtension = await super.getCompatibleVersion(extension, sameVersion, includePreRelease); if (compatibleExtension) { @@ -171,7 +170,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe throw new Error('This should not happen'); } if (e.preserveData) { - await this.webExtensionsScannerService.copyExtensions(previousProfileLocation, currentProfileLocation, e => !e.metadata?.isApplicationScoped); + await this.copyExtensions(previousProfileLocation, currentProfileLocation); this._onDidChangeProfile.fire({ added: [], removed: [] }); } else { const oldExtensions = await this.webExtensionsScannerService.scanUserExtensions(previousProfileLocation); diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts index ac3fd967300..693c9ae4cf5 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/nativeExtensionManagementService.ts @@ -5,98 +5,46 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { URI } from 'vs/base/common/uri'; -import { IGalleryExtension, ILocalExtension, InstallOptions, InstallVSIXOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { Emitter, Event } from 'vs/base/common/event'; +import { ILocalExtension, InstallVSIXOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { delta } from 'vs/base/common/arrays'; -import { compare } from 'vs/base/common/strings'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { joinPath } from 'vs/base/common/resources'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { Schemas } from 'vs/base/common/network'; import { ILogService } from 'vs/platform/log/common/log'; import { IDownloadService } from 'vs/platform/download/common/download'; import { IFileService } from 'vs/platform/files/common/files'; import { generateUuid } from 'vs/base/common/uuid'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ProfileAwareExtensionManagementChannelClient } from 'vs/workbench/services/extensionManagement/common/extensionManagementChannelClient'; -export class NativeExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - private readonly disposables = this._register(new DisposableStore()); - - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - override get onInstallExtension() { return Event.filter(this.onProfileAwareInstallExtension, e => this.filterEvent(e), this.disposables); } - - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - override get onDidInstallExtensions() { - return Event.filter( - Event.map(this.onProfileAwareDidInstallExtensions, results => results.filter(e => this.filterEvent(e)), this.disposables), - results => results.length > 0, this.disposables); - } - - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - override get onUninstallExtension() { return Event.filter(this.onProfileAwareUninstallExtension, e => this.filterEvent(e), this.disposables); } - - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } - override get onDidUninstallExtension() { return Event.filter(this.onProfileAwareDidUninstallExtension, e => this.filterEvent(e), this.disposables); } - - private readonly _onDidChangeProfile = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); - readonly onDidChangeProfile = this._onDidChangeProfile.event; +export class NativeExtensionManagementService extends ProfileAwareExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @IFileService private readonly fileService: IFileService, @IDownloadService private readonly downloadService: IDownloadService, @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, @ILogService private readonly logService: ILogService, ) { - super(channel); - this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); + super(channel, userDataProfileService, uriIdentityService); } - private filterEvent({ profileLocation, applicationScoped }: { profileLocation?: URI; applicationScoped?: boolean }): boolean { + protected filterEvent({ profileLocation, applicationScoped }: { readonly profileLocation?: URI; readonly applicationScoped?: boolean }): boolean { return applicationScoped || this.uriIdentityService.extUri.isEqual(this.userDataProfileService.currentProfile.extensionsResource, profileLocation); } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { const { location, cleanup } = await this.downloadVsix(vsix); try { - options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; return await super.install(location, options); } finally { await cleanup(); } } - override installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - installOptions = installOptions?.profileLocation ? installOptions : { ...installOptions, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; - return super.installFromGallery(extension, installOptions); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - options = options?.profileLocation ? options : { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource }; - return super.uninstall(extension, options); - } - - override getInstalled(type: ExtensionType | null = null, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.getInstalled(type, profileLocation); - } - - override getMetadata(local: ILocalExtension, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.getMetadata(local, profileLocation); - } - - override updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation: URI = this.userDataProfileService.currentProfile.extensionsResource): Promise { - return super.updateMetadata(local, metadata, profileLocation); - } - private async downloadVsix(vsix: URI): Promise<{ location: URI; cleanup: () => Promise }> { if (vsix.scheme === Schemas.file) { return { location: vsix, async cleanup() { } }; @@ -114,20 +62,4 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel }; return { location, cleanup }; } - - private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise { - const oldExtensions = await super.getInstalled(ExtensionType.User, e.previous.extensionsResource); - if (e.preserveData) { - const extensions: [ILocalExtension, Metadata | undefined][] = await Promise.all(oldExtensions - .filter(e => !e.isApplicationScoped) /* remove application scoped extensions */ - .map(async e => ([e, await this.getMetadata(e)]))); - await this.extensionsProfileScannerService.addExtensionsToProfile(extensions, e.profile.extensionsResource!); - this._onDidChangeProfile.fire({ added: [], removed: [] }); - } else { - const newExtensions = await this.getInstalled(ExtensionType.User); - const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); - this._onDidChangeProfile.fire({ added, removed }); - } - } - } diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts index faaa349995f..1eed257472b 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode, UninstallOptions, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ILocalExtension, IGalleryExtension, IExtensionGalleryService, InstallOperation, InstallOptions, InstallVSIXOptions, ExtensionManagementError, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -19,20 +18,22 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { Promises } from 'vs/base/common/async'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IFileService } from 'vs/platform/files/common/files'; +import { RemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/remoteExtensionManagementService'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -export class NativeRemoteExtensionManagementService extends ExtensionManagementChannelClient implements IProfileAwareExtensionManagementService { - - readonly onDidChangeProfile = Event.None; - get onProfileAwareInstallExtension() { return super.onInstallExtension; } - get onProfileAwareDidInstallExtensions() { return super.onDidInstallExtensions; } - get onProfileAwareUninstallExtension() { return super.onUninstallExtension; } - get onProfileAwareDidUninstallExtension() { return super.onDidUninstallExtension; } +export class NativeRemoteExtensionManagementService extends RemoteExtensionManagementService implements IProfileAwareExtensionManagementService { constructor( channel: IChannel, private readonly localExtensionManagementServer: IExtensionManagementServer, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IRemoteUserDataProfilesService remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @ILogService private readonly logService: ILogService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -40,50 +41,16 @@ export class NativeRemoteExtensionManagementService extends ExtensionManagementC @IFileService private readonly fileService: IFileService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, ) { - super(channel); - } - - override getInstalled(type: ExtensionType | null = null, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } - return super.getInstalled(type); - } - - override uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise { - if (options?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } - return super.uninstall(extension, options); + super(channel, userDataProfileService, userDataProfilesService, remoteUserDataProfilesService, uriIdentityService); } override async install(vsix: URI, options?: InstallVSIXOptions): Promise { - if (options?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } const local = await super.install(vsix, options); await this.installUIDependenciesAndPackedExtensions(local); return local; } - override getMetadata(local: ILocalExtension, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Getting metadata from a specific profile is not supported in remote scenario'); - } - return super.getMetadata(local, profileLocation); - } - - override updateMetadata(local: ILocalExtension, metadata: Partial, profileLocation?: URI): Promise { - if (profileLocation) { - throw new Error('Getting metadata from a specific profile is not supported in remote scenario'); - } - return super.updateMetadata(local, metadata, profileLocation); - } - override async installFromGallery(extension: IGalleryExtension, installOptions?: InstallOptions): Promise { - if (installOptions?.profileLocation) { - throw new Error('Installing extensions to a specific profile is not supported in remote scenario'); - } const local = await this.doInstallFromGallery(extension, installOptions); await this.installUIDependenciesAndPackedExtensions(local); return local; diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index ceeb59af01f..496d82be53a 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -31,6 +31,7 @@ import { IAutomatedWindow } from 'vs/platform/log/browser/log'; import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { dedupExtensions } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -52,6 +53,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataProfileService userDataProfileService: IUserDataProfileService, @@ -70,6 +72,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten extensionManifestPropertiesService, logService, remoteAgentService, + remoteExtensionsScannerService, lifecycleService, userDataProfileService ); @@ -86,7 +89,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten protected async _scanSingleExtension(extension: IExtension): Promise { if (extension.location.scheme === Schemas.vscodeRemote) { - return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); + return this._remoteExtensionsScannerService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); } const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, this._userDataProfileService.currentProfile.extensionsResource); @@ -205,7 +208,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten let [localExtensions, remoteEnv, remoteExtensions] = await Promise.all([ this._scanWebExtensions(), this._remoteAgentService.getEnvironment(), - this._remoteAgentService.scanExtensions() + this._remoteExtensionsScannerService.scanExtensions() ]); localExtensions = this._checkEnabledAndProposedAPI(localExtensions, false); remoteExtensions = this._checkEnabledAndProposedAPI(remoteExtensions, false); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index f88a36f7e21..ea6247d4f43 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -37,6 +37,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -188,6 +189,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @IExtensionManifestPropertiesService protected readonly _extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService protected readonly _logService: ILogService, @IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService protected readonly _remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUserDataProfileService protected readonly _userDataProfileService: IUserDataProfileService, ) { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts index c2276f89cde..45022c15cc3 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/nativeExtensionService.ts @@ -52,6 +52,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { LegacyNativeLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/nativeLocalProcessExtensionHost'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; export class NativeExtensionService extends AbstractExtensionService implements IExtensionService { @@ -77,6 +78,7 @@ export class NativeExtensionService extends AbstractExtensionService implements @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService logService: ILogService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IRemoteExtensionsScannerService remoteExtensionsScannerService: IRemoteExtensionsScannerService, @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @INativeHostService private readonly _nativeHostService: INativeHostService, @@ -100,6 +102,7 @@ export class NativeExtensionService extends AbstractExtensionService implements extensionManifestPropertiesService, logService, remoteAgentService, + remoteExtensionsScannerService, lifecycleService, userDataProfileService ); @@ -146,7 +149,7 @@ export class NativeExtensionService extends AbstractExtensionService implements protected _scanSingleExtension(extension: IExtension): Promise { if (extension.location.scheme === Schemas.vscodeRemote) { - return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); + return this._remoteExtensionsScannerService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); } return this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System); @@ -571,7 +574,7 @@ export class NativeExtensionService extends AbstractExtensionService implements // fetch the remote environment [remoteEnv, remoteExtensions] = await Promise.all([ this._remoteAgentService.getEnvironment(), - this._remoteAgentService.scanExtensions() + this._remoteExtensionsScannerService.scanExtensions() ]); if (!remoteEnv) { diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 88c83a94c72..7bf74c80b6d 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -29,7 +29,7 @@ import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestUserDataProfileService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestRemoteExtensionsScannerService, TestUserDataProfileService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { mock } from 'vs/base/test/common/mock'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; @@ -37,6 +37,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { @@ -183,6 +184,7 @@ suite('ExtensionService', () => { [IUserDataProfilesService, UserDataProfilesService], [IUserDataProfileService, TestUserDataProfileService], [IUriIdentityService, UriIdentityService], + [IRemoteExtensionsScannerService, TestRemoteExtensionsScannerService], ]); extService = instantiationService.get(IExtensionService); }); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentService.ts b/src/vs/workbench/services/remote/browser/remoteAgentService.ts index 2cdbe229327..3cc54d49eea 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentService.ts @@ -18,18 +18,20 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( webSocketFactory: IWebSocketFactory | null | undefined, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService, @ILogService logService: ILogService ) { - super(new BrowserSocketFactory(webSocketFactory), environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(new BrowserSocketFactory(webSocketFactory), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 884d1d28ef7..2b0ecd843a1 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -17,10 +17,8 @@ import { Emitter } from 'vs/base/common/event'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ILogService } from 'vs/platform/log/common/log'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IProductService } from 'vs/platform/product/common/productService'; -import { URI } from 'vs/base/common/uri'; -import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export abstract class AbstractRemoteAgentService extends Disposable implements IRemoteAgentService { @@ -32,6 +30,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I constructor( socketFactory: ISocketFactory, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService protected readonly _environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @@ -60,7 +59,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I if (!this._environment) { this._environment = this._withChannel( async (channel, connection) => { - const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority); + const env = await RemoteExtensionEnvironmentChannelClient.getEnvironmentData(channel, connection.remoteAuthority, this.userDataProfileService.currentProfile.isDefault ? undefined : this.userDataProfileService.currentProfile.id); this._remoteAuthorityResolverService._setAuthorityConnectionToken(connection.remoteAuthority, env.connectionToken); return env; }, @@ -77,37 +76,6 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I ); } - whenExtensionsReady(): Promise { - return this._withChannel( - channel => RemoteExtensionEnvironmentChannelClient.whenExtensionsReady(channel), - undefined - ); - } - - scanExtensions(skipExtensions: ExtensionIdentifier[] = []): Promise { - return this._withChannel( - async (channel, connection) => { - const scannedExtensions = await RemoteExtensionEnvironmentChannelClient.scanExtensions(channel, connection.remoteAuthority, this._environmentService.extensionDevelopmentLocationURI, skipExtensions); - scannedExtensions.forEach((extension) => ImplicitActivationEvents.updateManifest(extension)); - return scannedExtensions; - }, - [] - ).then(undefined, () => []); - } - - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { - return this._withChannel( - async (channel, connection) => { - const scannedExtension = await RemoteExtensionEnvironmentChannelClient.scanSingleExtension(channel, connection.remoteAuthority, isBuiltin, extensionLocation); - if (scannedExtension !== null) { - ImplicitActivationEvents.updateManifest(scannedExtension); - } - return scannedExtension; - }, - null - ).then(undefined, () => null); - } - getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { return this._withChannel( channel => RemoteExtensionEnvironmentChannelClient.getDiagnosticInfo(channel, options), @@ -162,6 +130,7 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I } return connection.withChannel('telemetry', (channel) => callback(channel, connection)); } + } class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection { diff --git a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts index 1973082e189..c5d56b6dffe 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts @@ -5,17 +5,18 @@ import * as platform from 'vs/base/common/platform'; import * as performance from 'vs/base/common/performance'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionHostExitInfo } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { Mutable } from 'vs/base/common/types'; +import { revive } from 'vs/base/common/marshalling'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IGetEnvironmentDataArguments { remoteAuthority: string; + profile?: string; } export interface IGetExtensionHostExitInfoArguments { @@ -23,20 +24,6 @@ export interface IGetExtensionHostExitInfoArguments { reconnectionToken: string; } -export interface IScanExtensionsArguments { - language: string; - remoteAuthority: string; - extensionDevelopmentPath: UriComponents[] | undefined; - skipExtensions: ExtensionIdentifier[]; -} - -export interface IScanSingleExtensionArguments { - language: string; - remoteAuthority: string; - isBuiltin: boolean; - extensionLocation: UriComponents; -} - export interface IRemoteAgentEnvironmentDTO { pid: number; connectionToken: string; @@ -52,13 +39,18 @@ export interface IRemoteAgentEnvironmentDTO { arch: string; marks: performance.PerformanceMark[]; useHostProxy: boolean; + profiles: { + all: UriDto; + home: UriComponents; + }; } export class RemoteExtensionEnvironmentChannelClient { - static async getEnvironmentData(channel: IChannel, remoteAuthority: string): Promise { + static async getEnvironmentData(channel: IChannel, remoteAuthority: string, profile: string | undefined): Promise { const args: IGetEnvironmentDataArguments = { - remoteAuthority + remoteAuthority, + profile }; const data = await channel.call('getEnvironmentData', args); @@ -77,7 +69,8 @@ export class RemoteExtensionEnvironmentChannelClient { os: data.os, arch: data.arch, marks: data.marks, - useHostProxy: data.useHostProxy + useHostProxy: data.useHostProxy, + profiles: revive(data.profiles) }; } @@ -89,39 +82,6 @@ export class RemoteExtensionEnvironmentChannelClient { return channel.call('getExtensionHostExitInfo', args); } - static async whenExtensionsReady(channel: IChannel): Promise { - await channel.call('whenExtensionsReady'); - } - - static async scanExtensions(channel: IChannel, remoteAuthority: string, extensionDevelopmentPath: URI[] | undefined, skipExtensions: ExtensionIdentifier[]): Promise { - const args: IScanExtensionsArguments = { - language: platform.language, - remoteAuthority, - extensionDevelopmentPath, - skipExtensions - }; - - const extensions = await channel.call('scanExtensions', args); - extensions.forEach(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); }); - - return extensions; - } - - static async scanSingleExtension(channel: IChannel, remoteAuthority: string, isBuiltin: boolean, extensionLocation: URI): Promise { - const args: IScanSingleExtensionArguments = { - language: platform.language, - remoteAuthority, - isBuiltin, - extensionLocation - }; - - const extension = await channel.call('scanSingleExtension', args); - if (extension) { - (>extension).extensionLocation = URI.revive(extension.extensionLocation); - } - return extension; - } - static getDiagnosticInfo(channel: IChannel, options: IDiagnosticInfoOptions): Promise { return channel.call('getDiagnosticInfo', options); } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index 0054090e83d..abaf81a080e 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -10,8 +10,6 @@ import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics import { Event } from 'vs/base/common/event'; import { PersistentConnectionEvent, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ITelemetryData, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; -import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; export const RemoteExtensionLogFileName = 'remoteagent'; @@ -42,15 +40,6 @@ export interface IRemoteAgentService { */ getRoundTripTime(): Promise; - whenExtensionsReady(): Promise; - /** - * Scan remote extensions. - */ - scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise; - /** - * Scan a single remote extension. - */ - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise; getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise; updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise; logTelemetry(eventName: string, data?: ITelemetryData): Promise; diff --git a/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts new file mode 100644 index 00000000000..7c499158b40 --- /dev/null +++ b/src/vs/workbench/services/remote/common/remoteExtensionsScanner.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteExtensionsScannerService, RemoteExtensionsScannerChannelName } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import * as platform from 'vs/base/common/platform'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionDescription, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { URI } from 'vs/base/common/uri'; +import { ImplicitActivationEvents } from 'vs/platform/extensionManagement/common/implicitActivationEvents'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IRemoteUserDataProfilesService } from 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +class RemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + + declare readonly _serviceBrand: undefined; + + constructor( + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService, + @ILogService private readonly logService: ILogService, + ) { } + + whenExtensionsReady(): Promise { + return this.withChannel( + channel => channel.call('whenExtensionsReady'), + undefined + ); + } + + async scanExtensions(): Promise { + try { + return await this.withChannel( + async (channel) => { + const profileLocation = this.userDataProfileService.currentProfile.isDefault ? undefined : (await this.remoteUserDataProfilesService.getRemoteProfile(this.userDataProfileService.currentProfile)).extensionsResource; + const scannedExtensions = await channel.call('scanExtensions', [platform.language, profileLocation, this.environmentService.extensionDevelopmentLocationURI]); + scannedExtensions.forEach((extension) => { + extension.extensionLocation = URI.revive(extension.extensionLocation); + ImplicitActivationEvents.updateManifest(extension); + }); + return scannedExtensions; + }, + [] + ); + } catch (error) { + this.logService.error(error); + return []; + } + } + + async scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { + try { + return await this.withChannel( + async (channel) => { + const extension = await channel.call('scanSingleExtension', [extensionLocation, isBuiltin, platform.language]); + if (extension !== null) { + extension.extensionLocation = URI.revive(extension.extensionLocation); + ImplicitActivationEvents.updateManifest(extension); + } + return extension; + }, + null + ); + } catch (error) { + this.logService.error(error); + return null; + } + } + + private withChannel(callback: (channel: IChannel) => Promise, fallback: R): Promise { + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return Promise.resolve(fallback); + } + return connection.withChannel(RemoteExtensionsScannerChannelName, (channel) => callback(channel)); + } +} + +registerSingleton(IRemoteExtensionsScannerService, RemoteExtensionsScannerService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts index 997cfc7cda5..4ab21828920 100644 --- a/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/electron-sandbox/remoteAgentService.ts @@ -20,16 +20,18 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class RemoteAgentService extends AbstractRemoteAgentService implements IRemoteAgentService { constructor( + @IUserDataProfileService userDataProfileService: IUserDataProfileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService signService: ISignService, @ILogService logService: ILogService, ) { - super(new BrowserSocketFactory(null), environmentService, productService, remoteAuthorityResolverService, signService, logService); + super(new BrowserSocketFactory(null), userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService); } } diff --git a/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts new file mode 100644 index 00000000000..d3ef612836b --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts @@ -0,0 +1,180 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { distinct } from 'vs/base/common/arrays'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; + +const associatedRemoteProfilesKey = 'associatedRemoteProfiles'; + +export const IRemoteUserDataProfilesService = createDecorator('IRemoteUserDataProfilesService'); +export interface IRemoteUserDataProfilesService { + readonly _serviceBrand: undefined; + getRemoteProfiles(): Promise; + getRemoteProfile(localProfile: IUserDataProfile): Promise; +} + +class RemoteUserDataProfilesService extends Disposable implements IRemoteUserDataProfilesService { + + readonly _serviceBrand: undefined; + + private readonly initPromise: Promise; + + private remoteUserDataProfilesService: IUserDataProfilesService | undefined; + + constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.initPromise = this.init(); + } + + private async init(): Promise { + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return; + } + + const environment = await this.remoteAgentService.getEnvironment(); + if (!environment) { + return; + } + + this.remoteUserDataProfilesService = new UserDataProfilesService(environment.profiles.all, environment.profiles.home, connection.getChannel('userDataProfiles')); + this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeLocalProfiles(e))); + + // Associate current local profile with remote profile + const remoteProfile = await this.getAssociatedRemoteProfile(this.userDataProfileService.currentProfile, this.remoteUserDataProfilesService); + if (!remoteProfile.isDefault) { + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), remoteProfile.id]); + } + + this.cleanUp(); + } + + private async onDidChangeLocalProfiles(e: DidChangeProfilesEvent): Promise { + for (const profile of e.removed) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profile.id); + if (remoteProfile) { + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + } + + async getRemoteProfiles(): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.remoteUserDataProfilesService.profiles; + } + + async getRemoteProfile(localProfile: IUserDataProfile): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.getAssociatedRemoteProfile(localProfile, this.remoteUserDataProfilesService); + } + + private async getAssociatedRemoteProfile(localProfile: IUserDataProfile, remoteUserDataProfilesService: IUserDataProfilesService): Promise { + // If the local profile is the default profile, return the remote default profile + if (localProfile.isDefault) { + return remoteUserDataProfilesService.defaultProfile; + } + + let profile = remoteUserDataProfilesService.profiles.find(p => p.id === localProfile.id); + if (!profile) { + profile = await remoteUserDataProfilesService.createProfile(localProfile.id, localProfile.name, { + shortName: localProfile.shortName, + transient: localProfile.isTransient, + useDefaultFlags: localProfile.useDefaultFlags, + }); + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), this.userDataProfileService.currentProfile.id]); + } + return profile; + } + + private getAssociatedRemoteProfiles(): string[] { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + return remotes[this.environmentService.remoteAuthority] ?? []; + } + return []; + } + + private setAssociatedRemoteProfiles(profiles: string[]): void { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + profiles = distinct(profiles); + if (profiles.length) { + remotes[this.environmentService.remoteAuthority] = profiles; + } else { + delete remotes[this.environmentService.remoteAuthority]; + } + if (Object.keys(remotes).length) { + this.storageService.store(associatedRemoteProfilesKey, JSON.stringify(remotes), StorageScope.APPLICATION, StorageTarget.MACHINE); + } else { + this.storageService.remove(associatedRemoteProfilesKey, StorageScope.APPLICATION); + } + } + } + + private parseAssociatedRemoteProfiles(): IStringDictionary { + if (this.environmentService.remoteAuthority) { + const value = this.storageService.get(associatedRemoteProfilesKey, StorageScope.APPLICATION); + try { + return value ? JSON.parse(value) : {}; + } catch (error) { + this.logService.error(error); + } + } + return {}; + } + + private async cleanUp(): Promise { + const associatedRemoteProfiles: string[] = []; + for (const profileId of this.getAssociatedRemoteProfiles()) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profileId); + if (!remoteProfile) { + continue; + } + const localProfile = this.userDataProfilesService.profiles.find(p => p.id === profileId); + if (localProfile) { + if (localProfile.name !== remoteProfile.name || localProfile.shortName !== remoteProfile.shortName) { + await this.remoteUserDataProfilesService?.updateProfile(remoteProfile, { name: localProfile.name, shortName: localProfile.shortName }); + } + associatedRemoteProfiles.push(profileId); + continue; + } + if (remoteProfile) { + // Cleanup remote profiles those are not available locally + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + this.setAssociatedRemoteProfiles(associatedRemoteProfiles); + } + +} + +registerSingleton(IRemoteUserDataProfilesService, RemoteUserDataProfilesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 078dd9d4f38..2a296009396 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -155,7 +155,7 @@ import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEdit import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService'; @@ -165,6 +165,7 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -1920,9 +1921,6 @@ export class TestRemoteAgentService implements IRemoteAgentService { async getEnvironment(): Promise { return null; } async getRawEnvironment(): Promise { return null; } async getExtensionHostExitInfo(reconnectionToken: string): Promise { return null; } - async whenExtensionsReady(): Promise { } - scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise { throw new Error('Method not implemented.'); } - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { throw new Error('Method not implemented.'); } async getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { return undefined; } async updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise { } async logTelemetry(eventName: string, data?: ITelemetryData): Promise { } @@ -1930,6 +1928,13 @@ export class TestRemoteAgentService implements IRemoteAgentService { async getRoundTripTime(): Promise { return undefined; } } +export class TestRemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + declare readonly _serviceBrand: undefined; + async whenExtensionsReady(): Promise { } + scanExtensions(): Promise { throw new Error('Method not implemented.'); } + scanSingleExtension(): Promise { throw new Error('Method not implemented.'); } +} + export class TestWorkbenchExtensionEnablementService implements IWorkbenchExtensionEnablementService { _serviceBrand: undefined; onEnablementChanged = Event.None; @@ -1996,9 +2001,6 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens getExtensionsControlManifest(): Promise { throw new Error('Method not implemented.'); } - getMetadata(extension: ILocalExtension): Promise | undefined> { - throw new Error('Method not implemented.'); - } async updateMetadata(local: ILocalExtension, metadata: Partial): Promise { return local; } registerParticipant(pariticipant: IExtensionManagementParticipant): void { } async getTargetPlatform(): Promise { return TargetPlatform.UNDEFINED; } @@ -2006,6 +2008,7 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens download(): Promise { throw new Error('Method not implemented.'); } + copyExtensions(): Promise { throw new Error('Not Supported'); } } export class TestUserDataProfileService implements IUserDataProfileService { diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index a2a0e92435f..2bb2ada89e6 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -87,7 +87,7 @@ export const TestNativeWindowConfiguration: INativeWindowConfiguration = { homeDir: homeDir, tmpDir: tmpdir(), userDataDir: getUserDataPath(args, product.nameShort), - profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE] }, + profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE], home: URI.file(homeDir) }, preferUtilityProcess: false, ...args }; diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 20342cec9fb..88e9eabaaaa 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -86,7 +86,9 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'; +import 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; import 'vs/workbench/services/remote/common/remoteExplorerService'; +import 'vs/workbench/services/remote/common/remoteExtensionsScanner'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/workingCopy/common/workingCopyEditorService';