mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 13:29:44 +00:00
- 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:
parent
14d922cd51
commit
9c0c68c8cf
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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']) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.`);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)));
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in a new issue