- Maintain default installed extensions manifest always
- Adopt to default installed extensions manifest
- Add externally installed extension to default extensions manifest
This commit is contained in:
Sandeep Somavarapu 2022-11-02 22:48:15 +01:00 committed by GitHub
parent 14d922cd51
commit 9c0c68c8cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 386 additions and 586 deletions

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* 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 { DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export class ExtensionsContributions extends Disposable {
constructor(
@INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
@IStorageService storageService: IStorageService,
@ILogService logService: ILogService,
) {
super();
extensionManagementService.migrateDefaultProfileExtensions()
.then(() => storageService.store(DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE), error => null);
extensionManagementService.removeUninstalledExtensions();
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
}
}

View file

@ -1,186 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, DidUninstallExtensionEvent, InstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { DidChangeProfilesEvent, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
const uninstalOptions: UninstallOptions = { versionOnly: true, donotIncludePack: true, donotCheckDependents: true };
export class ExtensionsCleaner extends Disposable {
constructor(
@INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@ILogService logService: ILogService,
) {
super();
extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1);
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
this._register(instantiationService.createInstance(ProfileExtensionsCleaner));
}
}
class ProfileExtensionsCleaner extends Disposable {
private profileExtensionsLocations = new Map<string, URI[]>;
private readonly profileModeDisposables = this._register(new MutableDisposable<DisposableStore>());
constructor(
@INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@ILogService private readonly logService: ILogService,
) {
super();
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
}
private async onDidChangeProfiles({ added, removed, all }: Omit<DidChangeProfilesEvent, 'updated'>): Promise<void> {
try {
await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
} catch (error) {
this.logService.error(error);
}
if (all.length === 1) {
// Exit profile mode
this.profileModeDisposables.clear();
// Listen for entering into profile mode
const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => {
disposable.dispose();
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
}));
return;
}
try {
if (added.length) {
await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
// Enter profile mode
if (!this.profileModeDisposables.value) {
this.profileModeDisposables.value = new DisposableStore();
this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear()));
this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e)));
this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
await this.uninstallExtensionsNotInProfiles();
}
}
} catch (error) {
this.logService.error(error);
}
}
private async uninstallExtensionsNotInProfiles(): Promise<void> {
const installed = await this.extensionManagementService.getAllUserInstalled();
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
if (toUninstall.length) {
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
}
}
private async onDidInstallExtensions(installedExtensions: readonly InstallExtensionResult[]): Promise<void> {
for (const { local, profileLocation } of installedExtensions) {
if (!local || !profileLocation) {
continue;
}
this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation);
}
}
private async onDidUninstallExtension(e: DidUninstallExtensionEvent): Promise<void> {
if (!e.profileLocation || !e.version) {
return;
}
if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) {
await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]);
}
}
private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise<void> {
const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation);
for (const extension of extensions) {
this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation);
}
}
private async removeExtensionsFromProfile(removedProfile: URI): Promise<void> {
const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = [];
for (const key of [...this.profileExtensionsLocations.keys()]) {
if (!this.removeExtensionWithKey(key, removedProfile)) {
continue;
}
const extensionToRemove = this.fromKey(key);
if (extensionToRemove) {
extensionsToRemove.push(extensionToRemove);
}
}
if (extensionsToRemove.length) {
await this.uninstallExtensions(extensionsToRemove);
}
}
private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void {
let locations = this.profileExtensionsLocations.get(key);
if (!locations) {
locations = [];
this.profileExtensionsLocations.set(key, locations);
}
locations.push(extensionsProfileLocation);
}
private removeExtensionWithKey(key: string, profileLocation: URI): boolean {
const profiles = this.profileExtensionsLocations.get(key);
if (profiles) {
const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation));
if (index > -1) {
profiles.splice(index, 1);
}
}
if (!profiles?.length) {
this.profileExtensionsLocations.delete(key);
return true;
}
return false;
}
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
const installed = await this.extensionManagementService.getAllUserInstalled();
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version)));
if (toUninstall.length) {
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
}
}
private getKey(identifier: IExtensionIdentifier, version: string): string {
return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`;
}
private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined {
const [id, version] = getIdAndVersion(key);
return version ? { identifier: { id }, version } : undefined;
}
}

View file

@ -14,7 +14,6 @@ import { URI } from 'vs/base/common/uri';
import { ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner';
import { ExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner';
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater';
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
@ -113,6 +112,7 @@ import { RemoteTunnelService } from 'vs/platform/remoteTunnel/electron-browser/r
import { IRemoteTunnelService } from 'vs/platform/remoteTunnel/common/remoteTunnel';
import { ISharedProcessLifecycleService, SharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService';
import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider';
import { ExtensionsContributions } from 'vs/code/electron-browser/sharedProcess/contrib/extensions';
class SharedProcessMain extends Disposable {
@ -186,7 +186,7 @@ class SharedProcessMain extends Disposable {
instantiationService.createInstance(UnusedWorkspaceStorageDataCleaner),
instantiationService.createInstance(LogsDataCleaner),
instantiationService.createInstance(LocalizationsUpdater),
instantiationService.createInstance(ExtensionsCleaner),
instantiationService.createInstance(ExtensionsContributions),
instantiationService.createInstance(UserDataProfilesCleaner)
));
}

View file

@ -101,7 +101,6 @@ import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-m
import { IPolicyService } from 'vs/platform/policy/common/policy';
import { PolicyChannel } from 'vs/platform/policy/common/policyIpc';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit';
import { RequestChannel } from 'vs/platform/request/common/requestIpc';
import { IRequestService } from 'vs/platform/request/common/request';
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
@ -558,9 +557,6 @@ export class CodeApplication extends Disposable {
// Auth Handler
this._register(instantiationService.createInstance(ProxyAuthHandler));
// Default Extensions Profile Init Handler
this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler));
// Transient profiles handler
this._register(instantiationService.createInstance(UserDataTransientProfilesHandler));
}

View file

@ -245,7 +245,7 @@ class CliMain extends Disposable {
}
private async doRun(environmentService: INativeEnvironmentService, fileService: IFileService, userDataProfilesService: IUserDataProfilesService, instantiationService: IInstantiationService): Promise<void> {
const profileLocation = environmentService.args.profile ? userDataProfilesService.profiles.find(p => p.name === environmentService.args.profile)?.extensionsResource : userDataProfilesService.defaultProfile.extensionsResource;
const profileLocation = (environmentService.args.profile ? userDataProfilesService.profiles.find(p => p.name === environmentService.args.profile) ?? userDataProfilesService.defaultProfile : userDataProfilesService.defaultProfile).extensionsResource;
// Install Source
if (this.argv['install-source']) {

View file

@ -24,6 +24,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
export type InstallExtensionTaskOptions = InstallOptions & InstallVSIXOptions & { readonly profileLocation: URI };
export interface IInstallExtensionTask {
readonly identifier: IExtensionIdentifier;
readonly source: IGalleryExtension | URI;
@ -34,6 +35,7 @@ export interface IInstallExtensionTask {
cancel(): void;
}
export type UninstallExtensionTaskOptions = UninstallOptions & { readonly profileLocation: URI };
export interface IUninstallExtensionTask {
readonly extension: ILocalExtension;
run(): Promise<void>;
@ -105,22 +107,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
return this.uninstallExtension(extension, options);
}
async reinstallFromGallery(extension: ILocalExtension): Promise<void> {
this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
if (!this.galleryService.isEnabled()) {
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
const targetPlatform = await this.getTargetPlatform();
const [galleryExtension] = await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: extension.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None);
if (!galleryExtension) {
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run();
await this.installFromGallery(galleryExtension);
}
getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {
const now = new Date().getTime();
@ -138,7 +124,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${options.profileLocation ? `-${options.profileLocation.toString()}` : ''}`;
const installExtensionTaskOptions: InstallExtensionTaskOptions = {
...options,
installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */
profileLocation: !options.profileLocation || isApplicationScopedExtension(manifest) ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation
};
const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`;
// only cache gallery extensions tasks
if (!URI.isUri(extension)) {
@ -148,28 +139,27 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const { local } = await installingExtension.task.waitUntilTaskIsFinished();
return local;
}
options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ };
}
const allInstallExtensionTasks: { task: IInstallExtensionTask; manifest: IExtensionManifest }[] = [];
const alreadyRequestedInstallations: Promise<void>[] = [];
const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = [];
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options);
const installExtensionTask = this.createInstallExtensionTask(manifest, extension, installExtensionTaskOptions);
if (!URI.isUri(extension)) {
this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] });
}
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation });
this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: installExtensionTaskOptions.profileLocation });
this.logService.info('Installing extension:', installExtensionTask.identifier.id);
allInstallExtensionTasks.push({ task: installExtensionTask, manifest });
let installExtensionHasDependents: boolean = false;
try {
if (options.donotIncludePackAndDependencies) {
if (installExtensionTaskOptions.donotIncludePackAndDependencies) {
this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
} else {
try {
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion, options.profileLocation);
const installed = await this.getInstalled(undefined, options.profileLocation);
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!installExtensionTaskOptions.installOnlyNewlyAddedFromExtensionPack, !!installExtensionTaskOptions.installPreReleaseVersion, installExtensionTaskOptions.profileLocation);
const installed = await this.getInstalled(undefined, installExtensionTaskOptions.profileLocation);
for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
const key = getInstallExtensionTaskKey(gallery);
@ -192,9 +182,9 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}));
}
} else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) {
const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true });
const task = this.createInstallExtensionTask(manifest, gallery, { ...installExtensionTaskOptions, donotIncludePackAndDependencies: true });
this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] });
this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: options.profileLocation });
this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: installExtensionTaskOptions.profileLocation });
this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id);
allInstallExtensionTasks.push({ task, manifest });
}
@ -238,7 +228,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
const startTime = new Date().getTime();
try {
const { local } = await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, options, CancellationToken.None)));
await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, installExtensionTaskOptions, CancellationToken.None)));
if (!URI.isUri(task.source)) {
const isUpdate = task.operation === InstallOperation.Update;
const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000;
@ -255,7 +245,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
} catch (error) { /* ignore */ }
}
}
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: options.context, profileLocation: options.profileLocation, applicationScoped: local.isApplicationScoped });
installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, applicationScoped: local.isApplicationScoped });
} catch (error) {
if (!URI.isUri(task.source)) {
reportTelemetry(this.telemetryService, task.operation === InstallOperation.Update ? 'extensionGallery:update' : 'extensionGallery:install', {
@ -287,7 +277,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
// rollback installed extensions
if (installResults.length) {
try {
const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: options.profileLocation }).run()));
const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: installExtensionTaskOptions.profileLocation }).run()));
for (let index = 0; index < result.length; index++) {
const r = result[index];
const { identifier } = installResults[index];
@ -303,7 +293,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
}
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: options.context, profileLocation: options.profileLocation })));
this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation })));
throw error;
} finally {
for (const [key, { task, waitingTasks }] of this.installingExtensions.entries()) {
@ -475,50 +465,54 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
private async uninstallExtension(extension: ILocalExtension, options: UninstallOptions): Promise<void> {
const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${options.versionOnly ? `-${extension.manifest.version}` : ''}${options.profileLocation ? `@${options.profileLocation.toString()}` : ''}`;
const uninstallOptions: UninstallExtensionTaskOptions = {
...options,
profileLocation: !options.profileLocation || extension.isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation
};
const getUninstallExtensionTaskKey = (identifier: IExtensionIdentifier) => `${identifier.id.toLowerCase()}${uninstallOptions.versionOnly ? `-${extension.manifest.version}` : ''}${uninstallOptions.profileLocation ? `@${uninstallOptions.profileLocation.toString()}` : ''}`;
const uninstallExtensionTask = this.uninstallingExtensions.get(getUninstallExtensionTaskKey(extension.identifier));
if (uninstallExtensionTask) {
this.logService.info('Extensions is already requested to uninstall', extension.identifier.id);
return uninstallExtensionTask.waitUntilTaskIsFinished();
}
const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: UninstallOptions): IUninstallExtensionTask => {
const createUninstallExtensionTask = (extension: ILocalExtension): IUninstallExtensionTask => {
const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions);
this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask);
if (options.profileLocation) {
this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString());
if (uninstallOptions.profileLocation) {
this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
} else {
this.logService.info('Uninstalling extension:', `${extension.identifier.id}@${extension.manifest.version}`);
}
this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped });
this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
return uninstallExtensionTask;
};
const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => {
if (error) {
if (options.profileLocation) {
this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString(), error.message);
if (uninstallOptions.profileLocation) {
this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString(), error.message);
} else {
this.logService.error('Failed to uninstall extension:', `${extension.identifier.id}@${extension.manifest.version}`, error.message);
}
} else {
if (options.profileLocation) {
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString());
if (uninstallOptions.profileLocation) {
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, uninstallOptions.profileLocation.toString());
} else {
this.logService.info('Successfully uninstalled extension:', `${extension.identifier.id}@${extension.manifest.version}`);
}
}
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, version: extension.manifest.version, error: error?.code, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, version: extension.manifest.version, error: error?.code, profileLocation: uninstallOptions.profileLocation, applicationScoped: extension.isApplicationScoped });
};
const allTasks: IUninstallExtensionTask[] = [];
const processedTasks: IUninstallExtensionTask[] = [];
try {
allTasks.push(createUninstallExtensionTask(extension, options));
const installed = await this.getInstalled(ExtensionType.User, options.profileLocation);
if (options.donotIncludePack) {
allTasks.push(createUninstallExtensionTask(extension));
const installed = await this.getInstalled(ExtensionType.User, uninstallOptions.profileLocation);
if (uninstallOptions.donotIncludePack) {
this.logService.info('Uninstalling the extension without including packed extension', `${extension.identifier.id}@${extension.manifest.version}`);
} else {
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
@ -526,12 +520,12 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
if (this.uninstallingExtensions.has(getUninstallExtensionTaskKey(packedExtension.identifier))) {
this.logService.info('Extensions is already requested to uninstall', packedExtension.identifier.id);
} else {
allTasks.push(createUninstallExtensionTask(packedExtension, options));
allTasks.push(createUninstallExtensionTask(packedExtension));
}
}
}
if (options.donotCheckDependents) {
if (uninstallOptions.donotCheckDependents) {
this.logService.info('Uninstalling the extension without checking dependents', `${extension.identifier.id}@${extension.manifest.version}`);
} else {
this.checkForDependents(allTasks.map(task => task.extension), installed, extension);
@ -541,7 +535,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
await this.joinAllSettled(allTasks.map(async task => {
try {
await task.run();
await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, options, CancellationToken.None)));
await this.joinAllSettled(this.participants.map(participant => participant.postUninstall(task.extension, uninstallOptions, CancellationToken.None)));
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
if (task.extension.identifier.uuid) {
try {
@ -652,20 +646,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
}
}
private createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask {
if (options.profileLocation && isApplicationScopedExtension(manifest)) {
options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource };
}
return this.doCreateInstallExtensionTask(manifest, extension, options);
}
private createUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask {
if (options.profileLocation && extension.isApplicationScoped) {
options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource };
}
return this.doCreateUninstallExtensionTask(extension, options);
}
abstract getTargetPlatform(): Promise<TargetPlatform>;
abstract zip(extension: ILocalExtension): Promise<URI>;
abstract unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
@ -673,13 +653,14 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI>;
abstract reinstallFromGallery(extension: ILocalExtension): Promise<void>;
abstract getMetadata(extension: ILocalExtension): Promise<Metadata | undefined>;
abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>;
protected abstract doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask;
protected abstract doCreateUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask;
protected abstract createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask;
protected abstract createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask;
}
export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {

View file

@ -16,6 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
export const WEB_EXTENSION_TAG = '__web_extension';
export const DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY = 'DEFAULT_PROFILE_EXTENSIONS_MIGRATION';
export function TargetPlatformToString(targetPlatform: TargetPlatform) {
switch (targetPlatform) {
@ -498,13 +499,11 @@ export interface IExtensionTipsService {
getAllWorkspacesTips(): Promise<IWorkspaceTips[]>;
}
export const ExtensionsLabel = localize('extensions', "Extensions");
export const ExtensionsLocalizedLabel = { value: ExtensionsLabel, original: 'Extensions' };
export const PreferencesLabel = localize('preferences', "Preferences");
export const PreferencesLocalizedLabel = { value: PreferencesLabel, original: 'Preferences' };
export interface CLIOutput {
log(s: string): void;
error(s: string): void;

View file

@ -54,7 +54,7 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
return this.withProfileExtensions(profileLocation);
}
addExtensionsToProfile(extensions: [IExtension, Metadata][], profileLocation: URI): Promise<IScannedProfileExtension[]> {
addExtensionsToProfile(extensions: [IExtension, Metadata | undefined][], profileLocation: URI): Promise<IScannedProfileExtension[]> {
return this.withProfileExtensions(profileLocation, profileExtensions => {
// Remove the existing extension to avoid duplicates
profileExtensions = profileExtensions.filter(e => extensions.some(([extension]) => !areSameExtensions(e.identifier, extension.identifier)));

View file

@ -190,8 +190,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
}
async scanUserExtensions(scanOptions: ScanOptions): Promise<IScannedExtension[]> {
this.logService.trace('Started scanning user extensions');
const extensionsScannerInput = await this.createExtensionScannerInput(scanOptions.profileLocation ?? this.userExtensionsLocation, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language);
const location = scanOptions.profileLocation ?? this.userExtensionsLocation;
this.logService.trace('Started scanning user extensions', location);
const extensionsScannerInput = await this.createExtensionScannerInput(location, !!scanOptions.profileLocation, ExtensionType.User, !scanOptions.includeUninstalled, scanOptions.language);
const extensionsScanner = scanOptions.useCache && !extensionsScannerInput.devMode && extensionsScannerInput.excludeObsolete ? this.userExtensionsCachedScanner : this.extensionsScanner;
let extensions = await extensionsScanner.scanExtensions(extensionsScannerInput);
extensions = await this.applyScanOptions(extensions, ExtensionType.User, scanOptions, true);

View file

@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { EXTENSIONS_RESOURCE_NAME } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
export class DefaultExtensionsProfileInitHandler extends Disposable {
constructor(
@IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService,
@IFileService private readonly fileService: IFileService,
@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@ILogService logService: ILogService,
) {
super();
if (userDataProfilesService.isEnabled()) {
this._register(userDataProfilesService.onWillCreateProfile(e => {
if (userDataProfilesService.profiles.length === 1) {
e.join(this.initialize());
}
}));
this._register(userDataProfilesService.onDidChangeProfiles(e => {
if (userDataProfilesService.profiles.length === 1) {
this.uninitialize();
}
}));
} else {
this.uninitialize().then(null, e => logService.error(e));
}
}
private async initialize(): Promise<void> {
/* Create and populate the default extensions profile resource */
const extensionsProfileResource = this.getDefaultExtensionsProfileResource();
try { await this.fileService.del(extensionsProfileResource); } catch (error) { /* ignore */ }
const userExtensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true });
await this.extensionsProfileScannerService.addExtensionsToProfile(userExtensions.map(e => [e, e.metadata]), extensionsProfileResource);
}
private async uninitialize(): Promise<void> {
/* Remove the default extensions profile resource */
try { await this.fileService.del(this.getDefaultExtensionsProfileResource()); } catch (error) { /* ignore */ }
}
private getDefaultExtensionsProfileResource(): URI {
return this.userDataProfilesService.defaultProfile.extensionsResource ?? joinPath(this.userDataProfilesService.defaultProfile.location, EXTENSIONS_RESOURCE_NAME);
}
}

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Promises, Queue } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@ -22,10 +23,10 @@ import { extract, ExtractError, IFile, zip } from 'vs/base/node/zip';
import * as nls from 'vs/nls';
import { IDownloadService } from 'vs/platform/download/common/download';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import {
ExtensionManagementError, ExtensionManagementErrorCode, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation,
Metadata, InstallOptions, InstallVSIXOptions, UninstallOptions
Metadata, InstallOptions, InstallVSIXOptions
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, computeTargetPlatform, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
@ -54,7 +55,9 @@ interface InstallableExtension {
export const INativeServerExtensionManagementService = refineServiceDecorator<IExtensionManagementService, INativeServerExtensionManagementService>(IExtensionManagementService);
export interface INativeServerExtensionManagementService extends IExtensionManagementService {
readonly _serviceBrand: undefined;
removeUninstalledExtensions(removeOutdated: boolean): Promise<void>;
migrateDefaultProfileExtensions(): Promise<void>;
markAsUninstalled(...extensions: ILocalExtension[]): Promise<void>;
removeUninstalledExtensions(): Promise<void>;
getAllUserInstalled(): Promise<ILocalExtension[]>;
}
@ -85,13 +88,13 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
const extensionsWatcher = this._register(new ExtensionsWatcher(this, fileService, environmentService, logService, uriIdentityService));
const extensionsWatcher = this._register(new ExtensionsWatcher(this, userDataProfilesService, extensionsProfileScannerService, extensionsScannerService, uriIdentityService, fileService, logService));
this._register(extensionsWatcher.onDidChangeExtensionsByAnotherSource(({ added, removed }) => {
if (added.length) {
this._onDidInstallExtensions.fire(added.map(local => ({ identifier: local.identifier, operation: InstallOperation.None, local })));
this._onDidInstallExtensions.fire(added);
}
removed.forEach(extension => this._onDidUninstallExtension.fire({ identifier: extension }));
removed.forEach(e => this._onDidUninstallExtension.fire(e));
}));
}
@ -131,7 +134,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
}
getAllUserInstalled(): Promise<ILocalExtension[]> {
return this.extensionsScanner.scanUserExtensions(false);
return this.extensionsScanner.scanAllUserExtensions(false);
}
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
@ -173,8 +176,37 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
return local;
}
removeUninstalledExtensions(removeOutdated: boolean): Promise<void> {
return this.extensionsScanner.cleanUp(removeOutdated);
async reinstallFromGallery(extension: ILocalExtension): Promise<void> {
this.logService.trace('ExtensionManagementService#reinstallFromGallery', extension.identifier.id);
if (!this.galleryService.isEnabled()) {
throw new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"));
}
const targetPlatform = await this.getTargetPlatform();
const [galleryExtension] = await this.galleryService.getExtensions([{ ...extension.identifier, preRelease: extension.preRelease }], { targetPlatform, compatible: true }, CancellationToken.None);
if (!galleryExtension) {
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
await this.extensionsScanner.setUninstalled(extension);
try {
await this.extensionsScanner.removeUninstalledExtension(extension);
} catch (e) {
throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)));
}
await this.installFromGallery(galleryExtension);
}
markAsUninstalled(...extensions: ILocalExtension[]): Promise<void> {
return this.extensionsScanner.setUninstalled(...extensions);
}
removeUninstalledExtensions(): Promise<void> {
return this.extensionsScanner.cleanUp();
}
migrateDefaultProfileExtensions(): Promise<void> {
return this.extensionsScanner.migrateDefaultProfileExtensions();
}
async download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
@ -200,7 +232,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
return { location, cleanup };
}
protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): IInstallExtensionTask {
protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask {
let installExtensionTask: IInstallExtensionTask | undefined;
if (URI.isUri(extension)) {
installExtensionTask = new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService);
@ -212,17 +244,11 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
installExtensionTask.waitUntilTaskIsFinished().then(() => this.installGalleryExtensionsTasks.delete(key));
}
}
if (options.profileLocation) {
return new InstallExtensionInProfileTask(installExtensionTask, options.profileLocation, this.extensionsProfileScannerService);
}
return installExtensionTask;
return new InstallExtensionInProfileTask(installExtensionTask, options.profileLocation, this.extensionsProfileScannerService);
}
protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask {
if (options.profileLocation) {
return new UninstallExtensionFromProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService);
}
return new UninstallExtensionTask(extension, options, this.extensionsScanner);
protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask {
return new UninstallExtensionFromProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService);
}
private async collectFiles(extension: ILocalExtension): Promise<IFile[]> {
@ -261,6 +287,8 @@ export class ExtensionsScanner extends Disposable {
private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
@IFileService private readonly fileService: IFileService,
@IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
@ILogService private readonly logService: ILogService,
) {
super();
@ -268,14 +296,12 @@ export class ExtensionsScanner extends Disposable {
this.uninstalledFileLimiter = new Queue();
}
async cleanUp(removeOutdated: boolean): Promise<void> {
async cleanUp(): Promise<void> {
await this.removeUninstalledExtensions();
if (removeOutdated) {
await this.removeOutdatedExtensions();
}
}
async scanExtensions(type: ExtensionType | null, profileLocation: URI | undefined): Promise<ILocalExtension[]> {
await this.migrateDefaultProfileExtensions();
const userScanOptions: ScanOptions = { includeInvalid: true, profileLocation };
let scannedExtensions: IScannedExtension[] = [];
if (type === null || type === ExtensionType.System) {
@ -287,12 +313,13 @@ export class ExtensionsScanner extends Disposable {
return Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension)));
}
async scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
async scanAllUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
const scannedExtensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: !excludeOutdated, includeInvalid: true });
return Promise.all(scannedExtensions.map(extension => this.toLocalExtension(extension)));
}
async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, metadata: Metadata, token: CancellationToken): Promise<ILocalExtension> {
await this.migrateDefaultProfileExtensions();
const folderName = extensionKey.toString();
const tempPath = path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, `.${generateUuid()}`);
const extensionPath = path.join(this.extensionsScannerService.userExtensionsLocation.fsPath, folderName);
@ -335,14 +362,16 @@ export class ExtensionsScanner extends Disposable {
async setUninstalled(...extensions: ILocalExtension[]): Promise<void> {
const extensionKeys: ExtensionKey[] = extensions.map(e => ExtensionKey.create(e));
await this.withUninstalledExtensions(uninstalled => {
extensionKeys.forEach(extensionKey => uninstalled[extensionKey.toString()] = true);
});
await this.withUninstalledExtensions(uninstalled =>
extensionKeys.forEach(extensionKey => {
uninstalled[extensionKey.toString()] = true;
this.logService.info('Marked extension as uninstalled', extensionKey.toString());
}));
}
async setInstalled(extensionKey: ExtensionKey): Promise<ILocalExtension | null> {
await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]);
const userExtensions = await this.scanUserExtensions(true);
const userExtensions = await this.scanAllUserExtensions(true);
const localExtension = userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)) || null;
if (!localExtension) {
return null;
@ -489,35 +518,33 @@ export class ExtensionsScanner extends Disposable {
await Promises.settled(toRemove.map(e => this.removeUninstalledExtension(e)));
}
private async removeOutdatedExtensions(): Promise<void> {
const systemExtensions = await this.extensionsScannerService.scanSystemExtensions({}); // System extensions
const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions
const toRemove: IScannedExtension[] = [];
// Outdated extensions
const targetPlatform = await this.extensionsScannerService.getTargetPlatform();
const byExtension = groupByExtension(extensions, e => e.identifier);
for (const extensions of byExtension) {
if (extensions.length > 1) {
toRemove.push(...extensions.sort((a, b) => {
const vcompare = semver.rcompare(a.manifest.version, b.manifest.version);
if (vcompare !== 0) {
return vcompare;
private _migrateDefaultProfileExtensionsPromise: Promise<void> | undefined = undefined;
migrateDefaultProfileExtensions(): Promise<void> {
if (!this._migrateDefaultProfileExtensionsPromise) {
this._migrateDefaultProfileExtensionsPromise = (async () => {
try {
const migrationMarkerFile = joinPath(this.extensionsScannerService.userExtensionsLocation, '.migrated-default-profile');
if (await this.fileService.exists(migrationMarkerFile)) {
return;
}
if (a.targetPlatform === targetPlatform) {
return -1;
if (!(await this.fileService.exists(this.userDataProfilesService.defaultProfile.extensionsResource))) {
this.logService.info('Started migrating default profile extensions from extensions installation folder to extensions manifest.');
const userExtensions = await this.extensionsScannerService.scanUserExtensions({ includeInvalid: true });
await this.extensionsProfileScannerService.addExtensionsToProfile(userExtensions.map(e => [e, e.metadata]), this.userDataProfilesService.defaultProfile.extensionsResource);
this.logService.info('Completed migrating default profile extensions from extensions installation folder to extensions manifest.');
}
return 1;
}).slice(1));
}
if (extensions[0].type === ExtensionType.System) {
const systemExtension = systemExtensions.find(e => areSameExtensions(e.identifier, extensions[0].identifier));
if (!systemExtension || semver.gte(systemExtension.manifest.version, extensions[0].manifest.version)) {
toRemove.push(extensions[0]);
try {
await this.fileService.createFile(migrationMarkerFile, VSBuffer.fromString(''));
} catch (error) {
this.logService.warn('Failed to create migration marker file for default profile extensions migration.', getErrorMessage(error));
}
} catch (error) {
this.logService.error(error);
throw error;
}
}
})();
}
await Promises.settled(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
return this._migrateDefaultProfileExtensionsPromise;
}
private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
@ -775,46 +802,6 @@ class InstallExtensionInProfileTask implements IInstallExtensionTask {
}
}
class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
constructor(
readonly extension: ILocalExtension,
private readonly options: UninstallOptions,
private readonly extensionsScanner: ExtensionsScanner,
) {
super();
}
protected async doRun(token: CancellationToken): Promise<void> {
const toUninstall: ILocalExtension[] = [];
const userExtensions = await this.extensionsScanner.scanUserExtensions(false);
if (this.options.versionOnly) {
const extensionKey = ExtensionKey.create(this.extension);
toUninstall.push(...userExtensions.filter(u => extensionKey.equals(ExtensionKey.create(u))));
} else {
toUninstall.push(...userExtensions.filter(u => areSameExtensions(u.identifier, this.extension.identifier)));
}
if (!toUninstall.length) {
throw new Error(nls.localize('notInstalled', "Extension '{0}' is not installed.", this.extension.manifest.displayName || this.extension.manifest.name));
}
await this.extensionsScanner.setUninstalled(...toUninstall);
if (this.options.remove) {
for (const extension of toUninstall) {
try {
if (!token.isCancellationRequested) {
await this.extensionsScanner.removeUninstalledExtension(extension);
}
} catch (e) {
throw new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e)));
}
}
}
}
}
class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask {
constructor(

View file

@ -6,42 +6,49 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { DidUninstallExtensionEvent, IExtensionManagementService, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { DidUninstallExtensionEvent, InstallExtensionResult, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionIdentifier, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { FileChangeType, IFileChange, IFileService } from 'vs/platform/files/common/files';
import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
export class ExtensionsWatcher extends Disposable {
private readonly _onDidChangeExtensionsByAnotherSource = this._register(new Emitter<{ added: ILocalExtension[]; removed: IExtensionIdentifier[] }>());
private readonly _onDidChangeExtensionsByAnotherSource = this._register(new Emitter<{ added: InstallExtensionResult[]; removed: DidUninstallExtensionEvent[] }>());
readonly onDidChangeExtensionsByAnotherSource = this._onDidChangeExtensionsByAnotherSource.event;
private startTimestamp = 0;
private installingExtensions: IExtensionIdentifier[] = [];
private installedExtensions: IExtensionIdentifier[] | undefined;
private readonly profileExtensionsLocations = new Map<string, URI[]>;
constructor(
private readonly extensionsManagementService: IExtensionManagementService,
@IFileService fileService: IFileService,
@INativeEnvironmentService environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
private readonly extensionManagementService: INativeServerExtensionManagementService,
private readonly userDataProfilesService: IUserDataProfilesService,
private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
private readonly extensionsScannerService: IExtensionsScannerService,
private readonly uriIdentityService: IUriIdentityService,
private readonly fileService: IFileService,
private readonly logService: ILogService,
) {
super();
this.extensionsManagementService.getInstalled().then(extensions => {
this.installedExtensions = extensions.map(e => e.identifier);
this.startTimestamp = Date.now();
});
this._register(extensionsManagementService.onInstallExtension(e => this.onInstallExtension(e)));
this._register(extensionsManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this._register(extensionsManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
this.initialize().then(() => this.registerListeners(), error => logService.error(error));
}
const extensionsResource = URI.file(environmentService.extensionsPath);
this._register(fileService.watch(extensionsResource));
this._register(Event.filter(fileService.onDidFilesChange, e => e.rawChanges.some(change => this.doesChangeAffects(change, extensionsResource)))(() => this.onDidChange()));
private async initialize(): Promise<void> {
await this.extensionManagementService.migrateDefaultProfileExtensions();
await this.onDidChangeProfiles(this.userDataProfilesService.profiles, []);
await this.uninstallExtensionsNotInProfiles();
}
private registerListeners(): void {
this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e.added, e.removed)));
this._register(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
this._register(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
this._register(this.fileService.watch(this.extensionsScannerService.userExtensionsLocation));
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.rawChanges.some(change => this.doesChangeAffects(change, this.extensionsScannerService.userExtensionsLocation)))(() => this.onDidChange()));
}
private doesChangeAffects(change: IFileChange, extensionsResource: URI): boolean {
@ -68,78 +75,136 @@ export class ExtensionsWatcher extends Disposable {
return true;
}
private onInstallExtension(e: InstallExtensionEvent): void {
this.addInstallingExtension(e.identifier);
}
private onDidInstallExtensions(results: readonly InstallExtensionResult[]): void {
for (const e of results) {
this.removeInstallingExtension(e.identifier);
if (e.local) {
this.addInstalledExtension(e.identifier);
}
}
}
private onDidUninstallExtension(e: DidUninstallExtensionEvent): void {
if (!e.error) {
this.removeInstalledExtension(e.identifier);
}
}
private addInstallingExtension(extension: IExtensionIdentifier) {
this.removeInstallingExtension(extension);
this.installingExtensions.push(extension);
}
private removeInstallingExtension(identifier: IExtensionIdentifier) {
this.installingExtensions = this.installingExtensions.filter(e => !areSameExtensions(e, identifier));
}
private addInstalledExtension(extension: IExtensionIdentifier): void {
if (this.installedExtensions) {
this.removeInstalledExtension(extension);
this.installedExtensions.push(extension);
}
}
private removeInstalledExtension(identifier: IExtensionIdentifier): void {
if (this.installedExtensions) {
this.installedExtensions = this.installedExtensions.filter(e => !areSameExtensions(e, identifier));
}
}
private async onDidChange(): Promise<void> {
if (this.installedExtensions) {
const extensions = await this.extensionsManagementService.getInstalled();
const added = extensions.filter(e => {
if ([...this.installingExtensions, ...this.installedExtensions!].some(identifier => areSameExtensions(identifier, e.identifier))) {
return false;
}
if (e.installedTimestamp && e.installedTimestamp > this.startTimestamp) {
this.logService.info('Detected extension installed from another source', e.identifier.id);
return true;
} else {
this.logService.info('Ignored extension installed by another source because of invalid timestamp', e.identifier.id);
return false;
}
});
const removed = this.installedExtensions.filter(identifier => {
// Extension being installed
if (this.installingExtensions.some(installingExtension => areSameExtensions(installingExtension, identifier))) {
return false;
}
if (extensions.every(e => !areSameExtensions(e.identifier, identifier))) {
this.logService.info('Detected extension removed from another source', identifier.id);
return true;
}
const installed = await this.extensionManagementService.getAllUserInstalled();
const added = installed.filter(e => {
if (e.installedTimestamp !== undefined) {
return false;
}
if (this.profileExtensionsLocations.has(this.getKey(e.identifier, e.manifest.version))) {
return false;
}
this.logService.info('Detected extension installed from another source', e.identifier.id);
return true;
});
if (added.length) {
await this.extensionsProfileScannerService.addExtensionsToProfile(added.map(e => [e, undefined]), this.userDataProfilesService.defaultProfile.extensionsResource);
this._onDidChangeExtensionsByAnotherSource.fire({
added: added.map(local => ({
identifier: local.identifier,
operation: InstallOperation.None,
profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource,
local
})),
removed: []
});
this.installedExtensions = extensions.map(e => e.identifier);
if (added.length || removed.length) {
this._onDidChangeExtensionsByAnotherSource.fire({ added, removed });
}
}
private async onDidChangeProfiles(added: readonly IUserDataProfile[], removed: readonly IUserDataProfile[]): Promise<void> {
try {
await Promise.all(removed.map(profile => this.removeExtensionsFromProfile(profile.extensionsResource)));
} catch (error) {
this.logService.error(error);
}
try {
if (added.length) {
await Promise.all(added.map(profile => this.populateExtensionsFromProfile(profile.extensionsResource)));
}
} catch (error) {
this.logService.error(error);
}
}
private async uninstallExtensionsNotInProfiles(): Promise<void> {
const installed = await this.extensionManagementService.getAllUserInstalled();
const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
if (toUninstall.length) {
await this.extensionManagementService.markAsUninstalled(...toUninstall);
}
}
private async onDidInstallExtensions(installedExtensions: readonly InstallExtensionResult[]): Promise<void> {
for (const { local, profileLocation } of installedExtensions) {
if (!local || !profileLocation) {
continue;
}
this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation);
}
}
private async onDidUninstallExtension(e: DidUninstallExtensionEvent): Promise<void> {
if (!e.profileLocation || !e.version) {
return;
}
if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) {
await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]);
}
}
private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise<void> {
const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation);
for (const extension of extensions) {
this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation);
}
}
private async removeExtensionsFromProfile(removedProfile: URI): Promise<void> {
const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = [];
for (const key of [...this.profileExtensionsLocations.keys()]) {
if (!this.removeExtensionWithKey(key, removedProfile)) {
continue;
}
const extensionToRemove = this.fromKey(key);
if (extensionToRemove) {
extensionsToRemove.push(extensionToRemove);
}
}
if (extensionsToRemove.length) {
await this.uninstallExtensions(extensionsToRemove);
}
}
private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void {
let locations = this.profileExtensionsLocations.get(key);
if (!locations) {
locations = [];
this.profileExtensionsLocations.set(key, locations);
}
locations.push(extensionsProfileLocation);
}
private removeExtensionWithKey(key: string, profileLocation: URI): boolean {
const profiles = this.profileExtensionsLocations.get(key);
if (profiles) {
const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation));
if (index > -1) {
profiles.splice(index, 1);
}
}
if (!profiles?.length) {
this.profileExtensionsLocations.delete(key);
return true;
}
return false;
}
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
const installed = await this.extensionManagementService.getAllUserInstalled();
const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version)));
if (toUninstall.length) {
await this.extensionManagementService.markAsUninstalled(...toUninstall);
}
}
private getKey(identifier: IExtensionIdentifier, version: string): string {
return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`;
}
private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined {
const [id, version] = getIdAndVersion(key);
return version ? { identifier: { id }, version } : undefined;
}
}

View file

@ -16,7 +16,6 @@ type BroadcastedProfileChanges = UriDto<Omit<DidChangeProfilesEvent, 'all'>>;
export class BrowserUserDataProfilesService extends UserDataProfilesService implements IUserDataProfilesService {
protected override readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = true;
private readonly changesBroadcastChannel: BroadcastDataChannel<BroadcastedProfileChanges>;
constructor(

View file

@ -7,7 +7,6 @@ import { hash } from 'vs/base/common/hash';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { basename, joinPath } from 'vs/base/common/resources';
import { isUndefined } from 'vs/base/common/types';
import { URI, UriDto } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -45,7 +44,7 @@ export interface IUserDataProfile {
readonly keybindingsResource: URI;
readonly tasksResource: URI;
readonly snippetsHome: URI;
readonly extensionsResource: URI | undefined;
readonly extensionsResource: URI;
readonly useDefaultFlags?: UseDefaultProfileFlags;
readonly isTransient?: boolean;
}
@ -63,7 +62,7 @@ export function isUserDataProfile(thing: unknown): thing is IUserDataProfile {
&& URI.isUri(candidate.keybindingsResource)
&& URI.isUri(candidate.tasksResource)
&& URI.isUri(candidate.snippetsHome)
&& (isUndefined(candidate.extensionsResource) || URI.isUri(candidate.extensionsResource))
&& URI.isUri(candidate.extensionsResource)
);
}
@ -138,8 +137,6 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
};
}
export const EXTENSIONS_RESOURCE_NAME = 'extensions.json';
export function toUserDataProfile(id: string, name: string, location: URI, options?: IUserDataProfileOptions): IUserDataProfile {
return {
id,
@ -152,7 +149,7 @@ export function toUserDataProfile(id: string, name: string, location: URI, optio
keybindingsResource: joinPath(location, 'keybindings.json'),
tasksResource: joinPath(location, 'tasks.json'),
snippetsHome: joinPath(location, 'snippets'),
extensionsResource: joinPath(location, EXTENSIONS_RESOURCE_NAME),
extensionsResource: joinPath(location, 'extensions.json'),
useDefaultFlags: options?.useDefaultFlags,
isTransient: options?.transient
};
@ -184,7 +181,6 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
readonly _serviceBrand: undefined;
protected enabled: boolean = false;
protected readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = false;
readonly profilesHome: URI;
get defaultProfile(): IUserDataProfile { return this.profiles[0]; }
@ -237,7 +233,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
let emptyWindow: IUserDataProfile | undefined;
const workspaces = new ResourceMap<IUserDataProfile>();
const defaultProfile = toUserDataProfile(hash(this.environmentService.userRoamingDataHome.path).toString(16), localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
profiles.unshift({ ...defaultProfile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 || this.transientProfilesObject.profiles.length > 0 ? defaultProfile.extensionsResource : undefined });
profiles.unshift({ ...defaultProfile, isDefault: true });
if (profiles.length) {
const profileAssicaitions = this.getStoredProfileAssociations();
if (profileAssicaitions.workspaces) {

View file

@ -54,13 +54,12 @@ suite('UserDataProfileService (Common)', () => {
assert.strictEqual(testObject.defaultProfile.settingsResource.toString(), joinPath(environmentService.userRoamingDataHome, 'settings.json').toString());
assert.strictEqual(testObject.defaultProfile.snippetsHome.toString(), joinPath(environmentService.userRoamingDataHome, 'snippets').toString());
assert.strictEqual(testObject.defaultProfile.tasksResource.toString(), joinPath(environmentService.userRoamingDataHome, 'tasks.json').toString());
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
assert.strictEqual(testObject.defaultProfile.extensionsResource.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});
test('profiles always include default profile', () => {
assert.deepStrictEqual(testObject.profiles.length, 1);
assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
test('create profile with id', async () => {
@ -116,7 +115,6 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles.length, 2);
assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
assert.deepStrictEqual(testObject.profiles[0].extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});
test('profiles include default profile with extension resource undefined when transiet prrofile is removed', async () => {
@ -125,7 +123,6 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles.length, 1);
assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
test('update named profile', async () => {

View file

@ -51,26 +51,22 @@ suite('UserDataProfileMainService', () => {
test('default profile', () => {
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
});
test('profiles always include default profile', () => {
assert.deepStrictEqual(testObject.profiles.length, 1);
assert.deepStrictEqual(testObject.profiles[0].isDefault, true);
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
test('default profile when there are profiles', async () => {
await testObject.createNamedProfile('test');
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});
test('default profile when profiles are removed', async () => {
const profile = await testObject.createNamedProfile('test');
await testObject.removeProfile(profile);
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
});
});

View file

@ -113,7 +113,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
constructor(
// profileLocation changes for default profile
public profile: IUserDataProfile,
profile: IUserDataProfile,
collection: string | undefined,
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@ -147,7 +147,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const builtinExtensions: IExtensionIdentifier[] = lastSyncUserData?.builtinExtensions || [];
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile);
const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.syncResource.profile);
if (remoteExtensions) {
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`);
@ -185,7 +185,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected async hasRemoteChanged(lastSyncUserData: ILastSyncUserData): Promise<boolean> {
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile);
const { localExtensions, ignoredExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.syncResource.profile);
const { remote } = merge(localExtensions, lastSyncExtensions, lastSyncExtensions, lastSyncUserData.skippedExtensions || [], ignoredExtensions, lastSyncUserData.builtinExtensions || []);
return remote !== null;
}
@ -239,7 +239,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profile.location);
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.syncResource.profile.extensionsResource);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions, resourcePreview.builtinExtensions);
const { local, remote } = mergeResult;
@ -253,7 +253,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.profile.location);
const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.syncResource.profile.extensionsResource);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
if (remoteExtensions !== null) {
@ -287,7 +287,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (localChange !== Change.None) {
await this.backupLocal(JSON.stringify(localExtensions));
skippedExtensions = await this.localExtensionsProvider.updateLocalExtensions(local.added, local.removed, local.updated, skippedExtensions, this.profile);
skippedExtensions = await this.localExtensionsProvider.updateLocalExtensions(local.added, local.removed, local.updated, skippedExtensions, this.syncResource.profile);
}
if (remote) {
@ -325,7 +325,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
async hasLocalData(): Promise<boolean> {
try {
const { localExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.profile);
const { localExtensions } = await this.localExtensionsProvider.getLocalExtensions(this.syncResource.profile);
if (localExtensions.some(e => e.installed || e.disabled)) {
return true;
}

View file

@ -180,15 +180,27 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i
await this.backupLocal(stringifyLocalProfiles(this.getLocalUserDataProfiles(), false));
const promises: Promise<any>[] = [];
for (const profile of local.added) {
promises.push(this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName }));
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Creating '${profile.name}' profile...`);
await this.userDataProfilesService.createProfile(profile.id, profile.name, { shortName: profile.shortName });
this.logService.info(`${this.syncResourceLogLabel}: Created profile '${profile.name}'.`);
})());
}
for (const profile of local.removed) {
promises.push(this.userDataProfilesService.removeProfile(profile));
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Removing '${profile.name}' profile...`);
await this.userDataProfilesService.removeProfile(profile);
this.logService.info(`${this.syncResourceLogLabel}: Removed profile '${profile.name}'.`);
})());
}
for (const profile of local.updated) {
const localProfile = this.userDataProfilesService.profiles.find(p => p.id === profile.id);
if (localProfile) {
promises.push(this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName }));
promises.push((async () => {
this.logService.trace(`${this.syncResourceLogLabel}: Updating '${profile.name}' profile...`);
await this.userDataProfilesService.updateProfile(localProfile, { name: profile.name, shortName: profile.shortName });
this.logService.info(`${this.syncResourceLogLabel}: Updated profile '${profile.name}'.`);
})());
} else {
this.logService.info(`${this.syncResourceLogLabel}: Could not find profile with id '${profile.id}' to update.`);
}

View file

@ -542,19 +542,6 @@ class ProfileSynchronizer extends Disposable {
@IEnvironmentService private readonly environmentService: IEnvironmentService,
) {
super();
if (this._profile.isDefault) {
this._register(userDataProfilesService.onDidChangeProfiles(() => {
if ((userDataProfilesService.defaultProfile.extensionsResource && !this._profile.extensionsResource) ||
(!userDataProfilesService.defaultProfile.extensionsResource && this._profile.extensionsResource)) {
this._profile = userDataProfilesService.defaultProfile;
for (const [synchronizer] of this._enabled) {
if (synchronizer instanceof ExtensionsSynchroniser) {
synchronizer.profile = this._profile;
}
}
}
}));
}
this._register(userDataSyncEnablementService.onDidChangeResourceEnablement(([syncResource, enablement]) => this.onDidChangeResourceEnablement(syncResource, enablement)));
this._register(toDisposable(() => this._enabled.splice(0, this._enabled.length).forEach(([, , disposable]) => disposable.dispose())));
for (const syncResource of ALL_SYNC_RESOURCES) {

View file

@ -29,6 +29,7 @@ import { IExtensionsScannerService, toExtensionDescription } from 'vs/platform/e
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';
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
export class RemoteAgentEnvironmentChannel implements IServerChannel {
@ -44,6 +45,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel {
private readonly _logService: ILogService,
private readonly _extensionHostStatusService: IExtensionHostStatusService,
private readonly _extensionsScannerService: IExtensionsScannerService,
private readonly _extensionManagementService: INativeServerExtensionManagementService,
) {
if (_environmentService.args['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!_environmentService.args['do-not-sync'], installPreReleaseVersion: !!_environmentService.args['pre-release'] };
@ -334,6 +336,7 @@ export class RemoteAgentEnvironmentChannel implements IServerChannel {
}
private async _scanInstalledExtensions(language: string): Promise<IExtensionDescription[]> {
await this._extensionManagementService.migrateDefaultProfileExtensions();
const scannedExtensions = await this._extensionsScannerService.scanUserExtensions({ language, useCache: true });
return scannedExtensions.map(e => toExtensionDescription(e, false));
}

View file

@ -196,7 +196,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, instantiationService.createInstance(ExtensionManagementCLI), logService, extensionHostStatusService, extensionsScannerService, extensionManagementService);
socketServer.registerChannel('remoteextensionsenvironment', remoteExtensionEnvironmentChannel);
const telemetryChannel = new ServerTelemetryChannel(accessor.get(IServerTelemetryService), oneDsAppender);
@ -219,7 +219,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
socketServer.registerChannel('credentials', credentialsChannel);
// clean up deprecated extensions
extensionManagementService.removeUninstalledExtensions(true);
extensionManagementService.removeUninstalledExtensions();
disposables.add(new ErrorTelemetry(accessor.get(ITelemetryService)));

View file

@ -2501,6 +2501,7 @@ export class ReinstallAction extends Action {
constructor(
id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@INotificationService private readonly notificationService: INotificationService,
@IHostService private readonly hostService: IHostService,
@ -2523,7 +2524,7 @@ export class ReinstallAction extends Action {
return this.extensionsWorkbenchService.queryLocal()
.then(local => {
const entries = local
.filter(extension => !extension.isBuiltin)
.filter(extension => !extension.isBuiltin && extension.server !== this.extensionManagementServerService.webExtensionManagementServer)
.map(extension => {
return {
id: extension.identifier.id,

View file

@ -371,7 +371,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return this.readSystemExtensions();
}
async scanUserExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
async scanUserExtensions(profileLocation: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
const extensions = new Map<string, IScannedExtension>();
// Custom builtin extensions defined through `additionalBuiltinExtensions` API
@ -410,7 +410,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return result;
}
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation?: URI): Promise<IScannedExtension | null> {
async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation: URI): Promise<IScannedExtension | null> {
if (extensionType === ExtensionType.System) {
const systemExtensions = await this.scanSystemExtensions();
return systemExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
@ -419,7 +419,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return userExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null;
}
async scanMetadata(extensionLocation: URI, profileLocation?: URI): Promise<Metadata | undefined> {
async scanMetadata(extensionLocation: URI, profileLocation: URI): Promise<Metadata | undefined> {
const extension = await this.scanExistingExtension(extensionLocation, ExtensionType.User, profileLocation);
return extension?.metadata;
}
@ -437,19 +437,19 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return null;
}
async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> {
async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI): Promise<IScannedExtension> {
const webExtension = await this.toWebExtensionFromGallery(galleryExtension, metadata);
return this.addWebExtension(webExtension, profileLocation);
}
async addExtension(location: URI, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> {
async addExtension(location: URI, metadata: Metadata, profileLocation: URI): Promise<IScannedExtension> {
const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, undefined, metadata);
const extension = await this.toScannedExtension(webExtension, false);
await this.addToInstalledExtensions([webExtension], profileLocation);
return extension;
}
async removeExtension(extension: IScannedExtension, profileLocation?: URI): Promise<void> {
async removeExtension(extension: IScannedExtension, profileLocation: URI): Promise<void> {
await this.writeInstalledExtensions(profileLocation, installedExtensions => installedExtensions.filter(installedExtension => !areSameExtensions(installedExtension.identifier, extension.identifier)));
}
@ -467,7 +467,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
}
}
private async addWebExtension(webExtension: IWebExtension, profileLocation?: URI): Promise<IScannedExtension> {
private async addWebExtension(webExtension: IWebExtension, profileLocation: URI): Promise<IScannedExtension> {
const isSystem = !!(await this.scanSystemExtensions()).find(e => areSameExtensions(e.identifier, webExtension.identifier));
const isBuiltin = !!webExtension.metadata?.isBuiltin;
const extension = await this.toScannedExtension(webExtension, isBuiltin);
@ -504,7 +504,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return extension;
}
private async addToInstalledExtensions(webExtensions: IWebExtension[], profileLocation?: URI): Promise<void> {
private async addToInstalledExtensions(webExtensions: IWebExtension[], profileLocation: URI): Promise<void> {
await this.writeInstalledExtensions(profileLocation, installedExtensions => {
// Remove the existing extension to avoid duplicates
installedExtensions = installedExtensions.filter(installedExtension => webExtensions.some(extension => !areSameExtensions(installedExtension.identifier, extension.identifier)));
@ -513,11 +513,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
});
}
private async scanInstalledExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
private async scanInstalledExtensions(profileLocation: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> {
let installedExtensions = await this.readInstalledExtensions(profileLocation);
// If current profile is not a default profile, then add the application extensions to the list
if (this.userDataProfilesService.defaultProfile.extensionsResource && !this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {
if (!this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {
// Remove application extensions from the non default profile
installedExtensions = installedExtensions.filter(i => !i.metadata?.isApplicationScoped);
// Add application extensions from the default profile to the list
@ -764,14 +764,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten
return this._migratePackageNLSUrisPromise;
}
private async readInstalledExtensions(profileLocation?: URI): Promise<IWebExtension[]> {
private async readInstalledExtensions(profileLocation: URI): Promise<IWebExtension[]> {
if (this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) {
await this.migratePackageNLSUris();
}
return this.withWebExtensions(profileLocation);
}
private writeInstalledExtensions(profileLocation: URI | undefined, updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> {
private writeInstalledExtensions(profileLocation: URI, updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> {
return this.withWebExtensions(profileLocation, updateFn);
}

View file

@ -167,15 +167,15 @@ export interface IWebExtensionsScannerService {
readonly _serviceBrand: undefined;
scanSystemExtensions(): Promise<IExtension[]>;
scanUserExtensions(profileLocation: URI | undefined, options?: ScanOptions): Promise<IScannedExtension[]>;
scanUserExtensions(profileLocation: URI, options?: ScanOptions): Promise<IScannedExtension[]>;
scanExtensionsUnderDevelopment(): Promise<IExtension[]>;
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation: URI | undefined): Promise<IScannedExtension | null>;
scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation: URI): Promise<IScannedExtension | null>;
addExtension(location: URI, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>;
addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>;
removeExtension(extension: IScannedExtension, profileLocation: URI | undefined): Promise<void>;
addExtension(location: URI, metadata: Metadata, profileLocation: URI): Promise<IScannedExtension>;
addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI): Promise<IScannedExtension>;
removeExtension(extension: IScannedExtension, profileLocation: URI): Promise<void>;
copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, filter: (extension: IScannedExtension) => boolean): Promise<void>;
scanMetadata(extensionLocation: URI, profileLocation: URI | undefined): Promise<Metadata | undefined>;
scanMetadata(extensionLocation: URI, profileLocation: URI): Promise<Metadata | undefined>;
scanExtensionManifest(extensionLocation: URI): Promise<IExtensionManifest | null>;
}

View file

@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions';
import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, Metadata, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IProfileAwareExtensionManagementService, IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, InstallExtensionTaskOptions, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IProductService } from 'vs/platform/product/common/productService';
@ -129,17 +129,11 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
return local;
}
protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions): IInstallExtensionTask {
if (!options.profileLocation) {
options = { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
}
protected createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallExtensionTaskOptions): IInstallExtensionTask {
return new InstallExtensionTask(manifest, extension, options, this.webExtensionsScannerService);
}
protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask {
if (!options.profileLocation) {
options = { ...options, profileLocation: this.userDataProfileService.currentProfile.extensionsResource };
}
protected createUninstallExtensionTask(extension: ILocalExtension, options: UninstallExtensionTaskOptions): IUninstallExtensionTask {
return new UninstallExtensionTask(extension, options, this.webExtensionsScannerService);
}
@ -148,6 +142,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
getManifest(vsix: URI): Promise<IExtensionManifest> { throw new Error('unsupported'); }
updateExtensionScope(): Promise<ILocalExtension> { throw new Error('unsupported'); }
download(): Promise<URI> { throw new Error('unsupported'); }
reinstallFromGallery(): Promise<void> { throw new Error('unsupported'); }
private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise<void> {
const previousProfileLocation = e.previous.extensionsResource;
@ -202,7 +197,7 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio
constructor(
manifest: IExtensionManifest,
private readonly extension: URI | IGalleryExtension,
private readonly options: InstallOptions,
private readonly options: InstallExtensionTaskOptions,
private readonly webExtensionsScannerService: IWebExtensionsScannerService,
) {
super();
@ -243,7 +238,7 @@ class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUni
constructor(
readonly extension: ILocalExtension,
private readonly options: UninstallOptions,
private readonly options: UninstallExtensionTaskOptions,
private readonly webExtensionsScannerService: IWebExtensionsScannerService,
) {
super();

View file

@ -15,7 +15,6 @@ 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 { EXTENSIONS_RESOURCE_NAME } from 'vs/platform/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';
@ -106,8 +105,7 @@ export class NativeExtensionManagementService extends ExtensionManagementChannel
}
private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise<void> {
const previousExtensionsResource = e.previous.extensionsResource ?? joinPath(e.previous.location, EXTENSIONS_RESOURCE_NAME);
const oldExtensions = await super.getInstalled(ExtensionType.User, previousExtensionsResource);
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 */

View file

@ -16,6 +16,9 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { timeout } from 'vs/base/common/async';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
export class CachedExtensionScanner {
@ -28,6 +31,8 @@ export class CachedExtensionScanner {
@IHostService private readonly _hostService: IHostService,
@IExtensionsScannerService private readonly _extensionsScannerService: IExtensionsScannerService,
@IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService,
@IUserDataProfilesService private readonly _userDataProfilesService: IUserDataProfilesService,
@IStorageService private readonly _storageService: IStorageService,
@ILogService private readonly _logService: ILogService,
) {
this.scannedExtensions = new Promise<IExtensionDescription[]>((resolve, reject) => {
@ -53,9 +58,10 @@ export class CachedExtensionScanner {
private async _scanInstalledExtensions(): Promise<IExtensionDescription[]> {
try {
const language = platform.language;
const profileLocation = this._userDataProfilesService.profiles.length === 1 && this._userDataProfileService.currentProfile.isDefault && !this._storageService.getBoolean(DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY, StorageScope.APPLICATION, false) ? undefined : this._userDataProfileService.currentProfile.extensionsResource;
const [scannedSystemExtensions, scannedUserExtensions] = await Promise.all([
this._extensionsScannerService.scanSystemExtensions({ language, useCache: true, checkControlFile: true }),
this._extensionsScannerService.scanUserExtensions({ language, profileLocation: this._userDataProfileService.currentProfile.extensionsResource, useCache: true })]);
this._extensionsScannerService.scanUserExtensions({ language, profileLocation, useCache: true })]);
const scannedDevelopedExtensions = await this._extensionsScannerService.scanExtensionsUnderDevelopment({ language }, [...scannedSystemExtensions, ...scannedUserExtensions]);
const system = scannedSystemExtensions.map(e => toExtensionDescription(e, false));
const user = scannedUserExtensions.map(e => toExtensionDescription(e, false));

View file

@ -34,15 +34,6 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi
super();
this._currentProfile = currentProfile;
this._register(userDataProfilesService.onDidChangeProfiles(e => {
/**
* If the current profile is default profile, then reset it because,
* In Desktop the extensions resource will be set/unset in the default profile when profiles are changed.
*/
if (this._currentProfile.isDefault) {
this._currentProfile = userDataProfilesService.defaultProfile;
return;
}
const updatedCurrentProfile = e.updated.find(p => this._currentProfile.id === p.id);
if (updatedCurrentProfile) {
this._currentProfile = updatedCurrentProfile;

View file

@ -68,7 +68,7 @@ const NULL_PROFILE = {
keybindingsResource: joinPath(URI.file(homeDir), 'keybindings.json'),
tasksResource: joinPath(URI.file(homeDir), 'tasks.json'),
snippetsHome: joinPath(URI.file(homeDir), 'snippets'),
extensionsResource: undefined
extensionsResource: joinPath(URI.file(homeDir), 'extensions.json')
};
export const TestNativeWindowConfiguration: INativeWindowConfiguration = {