Skip to content

Commit bc403a0

Browse files
authored
Merge pull request microsoft#153220 from microsoft/sandy081/secure-kingfisher
Improve extensions management in profiles
2 parents 5db26d0 + 59ca049 commit bc403a0

File tree

16 files changed

+329
-99
lines changed

16 files changed

+329
-99
lines changed

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

Lines changed: 164 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,186 @@
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';
7-
import { IExtensionGalleryService, IGlobalExtensionEnablementService, IServerExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
6+
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
7+
import { URI } from 'vs/base/common/uri';
8+
import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, ServerDidUninstallExtensionEvent, ServerInstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
9+
import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
10+
import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
811
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
912
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
10-
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
13+
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
14+
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
15+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1116
import { ILogService } from 'vs/platform/log/common/log';
1217
import { IStorageService } from 'vs/platform/storage/common/storage';
18+
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
19+
import { DidChangeProfilesEvent, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
20+
21+
const uninstalOptions: UninstallOptions = { versionOnly: true, donotIncludePack: true, donotCheckDependents: true };
1322

1423
export class ExtensionsCleaner extends Disposable {
1524

1625
constructor(
17-
@IServerExtensionManagementService extensionManagementService: ExtensionManagementService,
26+
@INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService,
27+
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
1828
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
1929
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
2030
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
31+
@IInstantiationService instantiationService: IInstantiationService,
2132
@IStorageService storageService: IStorageService,
2233
@ILogService logService: ILogService,
2334
) {
2435
super();
25-
extensionManagementService.removeDeprecatedExtensions();
36+
37+
extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length > 1);
2638
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
2739
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
40+
this._register(instantiationService.createInstance(ProfileExtensionsCleaner));
2841
}
42+
43+
}
44+
45+
class ProfileExtensionsCleaner extends Disposable {
46+
47+
private profileExtensionsLocations = new Map<string, URI[]>;
48+
49+
private readonly profileModeDisposables = this._register(new MutableDisposable<DisposableStore>());
50+
51+
constructor(
52+
@INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService,
53+
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
54+
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
55+
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
56+
@ILogService private readonly logService: ILogService,
57+
) {
58+
super();
59+
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
60+
}
61+
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);
67+
}
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+
80+
try {
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+
}
92+
}
93+
} catch (error) {
94+
this.logService.error(error);
95+
}
96+
}
97+
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)));
103+
}
104+
}
105+
106+
private async onDidInstallExtensions(installedExtensions: readonly ServerInstallExtensionResult[]): Promise<void> {
107+
for (const { local, profileLocation } of installedExtensions) {
108+
if (!local || !profileLocation) {
109+
continue;
110+
}
111+
this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation);
112+
}
113+
}
114+
115+
private async onDidUninstallExtension(e: ServerDidUninstallExtensionEvent): Promise<void> {
116+
if (!e.profileLocation || !e.version) {
117+
return;
118+
}
119+
if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) {
120+
await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]);
121+
}
122+
}
123+
124+
private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise<void> {
125+
const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation);
126+
for (const extension of extensions) {
127+
this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation);
128+
}
129+
}
130+
131+
private async removeExtensionsFromProfile(removedProfile: URI): Promise<void> {
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+
}
142+
if (extensionsToRemove.length) {
143+
await this.uninstallExtensions(extensionsToRemove);
144+
}
145+
}
146+
147+
private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void {
148+
let locations = this.profileExtensionsLocations.get(key);
149+
if (!locations) {
150+
locations = [];
151+
this.profileExtensionsLocations.set(key, locations);
152+
}
153+
locations.push(extensionsProfileLocation);
154+
}
155+
156+
private removeExtensionWithKey(key: string, profileLocation: URI): boolean {
157+
const profiles = this.profileExtensionsLocations.get(key);
158+
if (profiles) {
159+
const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation));
160+
if (index > -1) {
161+
profiles.splice(index, 1);
162+
}
163+
}
164+
if (!profiles?.length) {
165+
this.profileExtensionsLocations.delete(key);
166+
return true;
167+
}
168+
return false;
169+
}
170+
171+
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
172+
const installed = await this.extensionManagementService.getAllUserInstalled();
173+
const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version)));
174+
if (toUninstall.length) {
175+
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
176+
}
177+
}
178+
179+
private getKey(identifier: IExtensionIdentifier, version: string): string {
180+
return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`;
181+
}
182+
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+
29188
}

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/
3434
import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
3535
import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc';
3636
import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService';
37-
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
37+
import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
3838
import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
3939
import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc';
4040
import { IFileService } from 'vs/platform/files/common/files';
@@ -315,7 +315,7 @@ class SharedProcessMain extends Disposable {
315315
// Extension Management
316316
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService));
317317
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService));
318-
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
318+
services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
319319

320320
// Extension Gallery
321321
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));

src/vs/code/node/cliProcessMain.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
2323
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
2424
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
2525
import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
26-
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
26+
import { IExtensionGalleryService, IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
2727
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
2828
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
2929
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
30-
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
30+
import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
3131
import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService';
3232
import { IFileService } from 'vs/platform/files/common/files';
3333
import { FileService } from 'vs/platform/files/common/fileService';
@@ -178,7 +178,7 @@ class CliMain extends Disposable {
178178
// Extensions
179179
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService));
180180
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService));
181-
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
181+
services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
182182
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService));
183183
services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService));
184184

0 commit comments

Comments
 (0)