Skip to content

Commit 59ca049

Browse files
committed
- fix clean up logic
- fix installing other versions do not uninstall in profile mode - add more logging
1 parent 36f67de commit 59ca049

File tree

3 files changed

+65
-45
lines changed

3 files changed

+65
-45
lines changed

src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Disposable } from 'vs/base/common/lifecycle';
6+
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
77
import { URI } from 'vs/base/common/uri';
88
import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, ServerDidUninstallExtensionEvent, ServerInstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
9+
import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
910
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
1011
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
1112
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
@@ -44,7 +45,8 @@ export class ExtensionsCleaner extends Disposable {
4445
class ProfileExtensionsCleaner extends Disposable {
4546

4647
private profileExtensionsLocations = new Map<string, URI[]>;
47-
private initPromise: Promise<boolean>;
48+
49+
private readonly profileModeDisposables = this._register(new MutableDisposable<DisposableStore>());
4850

4951
constructor(
5052
@INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService,
@@ -54,47 +56,54 @@ class ProfileExtensionsCleaner extends Disposable {
5456
@ILogService private readonly logService: ILogService,
5557
) {
5658
super();
57-
58-
this.initPromise = this.initialize();
59-
this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e)));
60-
this._register(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
61-
this._register(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
59+
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
6260
}
6361

64-
private async initialize(): Promise<boolean> {
65-
if (this.userDataProfilesService.profiles.length === 1) {
66-
return true;
62+
private async onDidChangeProfiles({ added, removed, all }: DidChangeProfilesEvent): Promise<void> {
63+
try {
64+
await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
65+
} catch (error) {
66+
this.logService.error(error);
6767
}
68+
69+
if (all.length === 0) {
70+
// Exit profile mode
71+
this.profileModeDisposables.clear();
72+
// Listen for entering into profile mode
73+
const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => {
74+
disposable.dispose();
75+
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
76+
}));
77+
return;
78+
}
79+
6880
try {
69-
const installed = await this.extensionManagementService.getAllUserInstalled();
70-
await Promise.all(this.userDataProfilesService.profiles.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
71-
const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
72-
if (toUninstall.length) {
73-
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
81+
if (added.length) {
82+
await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
83+
// Enter profile mode
84+
if (!this.profileModeDisposables.value) {
85+
this.profileModeDisposables.value = new DisposableStore();
86+
this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear()));
87+
this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e)));
88+
this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
89+
this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
90+
await this.uninstallExtensionsNotInProfiles();
91+
}
7492
}
75-
return true;
7693
} catch (error) {
77-
this.logService.error('ExtensionsCleaner: Failed to initialize');
7894
this.logService.error(error);
79-
return false;
8095
}
8196
}
8297

83-
private async onDidChangeProfiles({ added, removed, all }: DidChangeProfilesEvent): Promise<void> {
84-
if (!(await this.initPromise)) {
85-
return;
86-
}
87-
await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
88-
await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
89-
if (all.length === 1) {
90-
this.profileExtensionsLocations.clear();
98+
private async uninstallExtensionsNotInProfiles(): Promise<void> {
99+
const installed = await this.extensionManagementService.getAllUserInstalled();
100+
const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
101+
if (toUninstall.length) {
102+
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
91103
}
92104
}
93105

94106
private async onDidInstallExtensions(installedExtensions: readonly ServerInstallExtensionResult[]): Promise<void> {
95-
if (!(await this.initPromise)) {
96-
return;
97-
}
98107
for (const { local, profileLocation } of installedExtensions) {
99108
if (!local || !profileLocation) {
100109
continue;
@@ -107,9 +116,6 @@ class ProfileExtensionsCleaner extends Disposable {
107116
if (!e.profileLocation || !e.version) {
108117
return;
109118
}
110-
if (!(await this.initPromise)) {
111-
return;
112-
}
113119
if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) {
114120
await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]);
115121
}
@@ -123,8 +129,16 @@ class ProfileExtensionsCleaner extends Disposable {
123129
}
124130

125131
private async removeExtensionsFromProfile(removedProfile: URI): Promise<void> {
126-
const profileExtensions = await this.extensionsProfileScannerService.scanProfileExtensions(removedProfile);
127-
const extensionsToRemove = profileExtensions.filter(profileExtension => this.removeExtensionWithKey(this.getKey(profileExtension.identifier, profileExtension.version), removedProfile));
132+
const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = [];
133+
for (const key of [...this.profileExtensionsLocations.keys()]) {
134+
if (!this.removeExtensionWithKey(key, removedProfile)) {
135+
continue;
136+
}
137+
const extensionToRemove = this.fromKey(key);
138+
if (extensionToRemove) {
139+
extensionsToRemove.push(extensionToRemove);
140+
}
141+
}
128142
if (extensionsToRemove.length) {
129143
await this.uninstallExtensions(extensionsToRemove);
130144
}
@@ -149,8 +163,9 @@ class ProfileExtensionsCleaner extends Disposable {
149163
}
150164
if (!profiles?.length) {
151165
this.profileExtensionsLocations.delete(key);
166+
return true;
152167
}
153-
return !profiles?.length;
168+
return false;
154169
}
155170

156171
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
@@ -165,4 +180,9 @@ class ProfileExtensionsCleaner extends Disposable {
165180
return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`;
166181
}
167182

183+
private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined {
184+
const [id, version] = getIdAndVersion(key);
185+
return version ? { identifier: { id }, version } : undefined;
186+
}
187+
168188
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,9 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
436436
const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions, options.profileLocation);
437437
this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask);
438438
if (options.profileLocation) {
439-
this.logService.info('Uninstalling extension from the profile:', extension.identifier.id, options.profileLocation.toString());
439+
this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString());
440440
} else {
441-
this.logService.info('Uninstalling extension:', extension.identifier.id);
441+
this.logService.info('Uninstalling extension:', `${extension.identifier.id}@${extension.manifest.version}`);
442442
}
443443
this._onUninstallExtension.fire({ identifier: extension.identifier, profileLocation: options.profileLocation, applicationScoped: extension.isApplicationScoped });
444444
return uninstallExtensionTask;
@@ -447,15 +447,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
447447
const postUninstallExtension = (extension: ILocalExtension, error?: ExtensionManagementError): void => {
448448
if (error) {
449449
if (options.profileLocation) {
450-
this.logService.error('Failed to uninstall extension from the profile:', extension.identifier.id, options.profileLocation.toString(), error.message);
450+
this.logService.error('Failed to uninstall extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString(), error.message);
451451
} else {
452-
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
452+
this.logService.error('Failed to uninstall extension:', `${extension.identifier.id}@${extension.manifest.version}`, error.message);
453453
}
454454
} else {
455455
if (options.profileLocation) {
456-
this.logService.info('Successfully uninstalled extension from the profile', extension.identifier.id, options.profileLocation.toString());
456+
this.logService.info('Successfully uninstalled extension from the profile', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString());
457457
} else {
458-
this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
458+
this.logService.info('Successfully uninstalled extension:', `${extension.identifier.id}@${extension.manifest.version}`);
459459
}
460460
}
461461
reportTelemetry(this.telemetryService, 'extensionGallery:uninstall', { extensionData: getLocalExtensionTelemetryData(extension), error });
@@ -469,7 +469,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
469469
allTasks.push(createUninstallExtensionTask(extension, options));
470470
const installed = await this.getInstalled(ExtensionType.User, options.profileLocation);
471471
if (options.donotIncludePack) {
472-
this.logService.info('Uninstalling the extension without including packed extension', extension.identifier.id);
472+
this.logService.info('Uninstalling the extension without including packed extension', `${extension.identifier.id}@${extension.manifest.version}`);
473473
} else {
474474
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
475475
for (const packedExtension of packedExtensions) {
@@ -482,7 +482,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
482482
}
483483

484484
if (options.donotCheckDependents) {
485-
this.logService.info('Uninstalling the extension without checking dependents', extension.identifier.id);
485+
this.logService.info('Uninstalling the extension without checking dependents', `${extension.identifier.id}@${extension.manifest.version}`);
486486
} else {
487487
this.checkForDependents(allTasks.map(task => task.extension), installed, extension);
488488
}

src/vs/platform/extensionManagement/node/extensionManagementService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ class InstallGalleryExtensionTask extends InstallExtensionTask {
595595
const zipPath = await this.downloadExtension(this.gallery, this._operation);
596596
try {
597597
const local = await this.installExtension({ zipPath, key: ExtensionKey.create(this.gallery), metadata }, token);
598-
if (existingExtension && (existingExtension.targetPlatform !== local.targetPlatform || semver.neq(existingExtension.manifest.version, local.manifest.version))) {
598+
if (existingExtension && !this.options.profileLocation && (existingExtension.targetPlatform !== local.targetPlatform || semver.neq(existingExtension.manifest.version, local.manifest.version))) {
599599
await this.extensionsScanner.setUninstalled(existingExtension);
600600
}
601601
return { local, metadata };
@@ -664,7 +664,7 @@ class InstallVSIXTask extends InstallExtensionTask {
664664
} catch (e) {
665665
throw new Error(nls.localize('restartCode', "Please restart VS Code before reinstalling {0}.", this.manifest.displayName || this.manifest.name));
666666
}
667-
} else if (semver.gt(existing.manifest.version, this.manifest.version)) {
667+
} else if (!this.options.profileLocation && semver.gt(existing.manifest.version, this.manifest.version)) {
668668
await this.extensionsScanner.setUninstalled(existing);
669669
}
670670
} else {

0 commit comments

Comments
 (0)