Skip to content

Commit d9ed6d1

Browse files
authored
1 parent 4efa993 commit d9ed6d1

29 files changed

+570
-370
lines changed

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

-33
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
7+
import { URI } from 'vs/base/common/uri';
8+
import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, DidUninstallExtensionEvent, InstallExtensionResult, 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';
11+
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
12+
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
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';
16+
import { ILogService } from 'vs/platform/log/common/log';
17+
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 };
22+
23+
export class ExtensionsCleaner extends Disposable {
24+
25+
constructor(
26+
@INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService,
27+
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
28+
@IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
29+
@IExtensionStorageService extensionStorageService: IExtensionStorageService,
30+
@IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService,
31+
@IInstantiationService instantiationService: IInstantiationService,
32+
@IStorageService storageService: IStorageService,
33+
@ILogService logService: ILogService,
34+
) {
35+
super();
36+
37+
extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1);
38+
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
39+
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);
40+
this._register(instantiationService.createInstance(ProfileExtensionsCleaner));
41+
}
42+
}
43+
44+
class ProfileExtensionsCleaner extends Disposable {
45+
46+
private profileExtensionsLocations = new Map<string, URI[]>;
47+
48+
private readonly profileModeDisposables = this._register(new MutableDisposable<DisposableStore>());
49+
50+
constructor(
51+
@INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService,
52+
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
53+
@IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService,
54+
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
55+
@ILogService private readonly logService: ILogService,
56+
) {
57+
super();
58+
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
59+
}
60+
61+
private async onDidChangeProfiles({ added, removed, all }: Omit<DidChangeProfilesEvent, 'updated'>): Promise<void> {
62+
try {
63+
await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
64+
} catch (error) {
65+
this.logService.error(error);
66+
}
67+
68+
if (all.length === 1) {
69+
// Exit profile mode
70+
this.profileModeDisposables.clear();
71+
// Listen for entering into profile mode
72+
const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => {
73+
disposable.dispose();
74+
this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles });
75+
}));
76+
return;
77+
}
78+
79+
try {
80+
if (added.length) {
81+
await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve()));
82+
// Enter profile mode
83+
if (!this.profileModeDisposables.value) {
84+
this.profileModeDisposables.value = new DisposableStore();
85+
this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear()));
86+
this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e)));
87+
this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e)));
88+
this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
89+
await this.uninstallExtensionsNotInProfiles();
90+
}
91+
}
92+
} catch (error) {
93+
this.logService.error(error);
94+
}
95+
}
96+
97+
private async uninstallExtensionsNotInProfiles(): Promise<void> {
98+
const installed = await this.extensionManagementService.getAllUserInstalled();
99+
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
100+
if (toUninstall.length) {
101+
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
102+
}
103+
}
104+
105+
private async onDidInstallExtensions(installedExtensions: readonly InstallExtensionResult[]): Promise<void> {
106+
for (const { local, profileLocation } of installedExtensions) {
107+
if (!local || !profileLocation) {
108+
continue;
109+
}
110+
this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation);
111+
}
112+
}
113+
114+
private async onDidUninstallExtension(e: DidUninstallExtensionEvent): Promise<void> {
115+
if (!e.profileLocation || !e.version) {
116+
return;
117+
}
118+
if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) {
119+
await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]);
120+
}
121+
}
122+
123+
private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise<void> {
124+
const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation);
125+
for (const extension of extensions) {
126+
this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation);
127+
}
128+
}
129+
130+
private async removeExtensionsFromProfile(removedProfile: URI): Promise<void> {
131+
const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = [];
132+
for (const key of [...this.profileExtensionsLocations.keys()]) {
133+
if (!this.removeExtensionWithKey(key, removedProfile)) {
134+
continue;
135+
}
136+
const extensionToRemove = this.fromKey(key);
137+
if (extensionToRemove) {
138+
extensionsToRemove.push(extensionToRemove);
139+
}
140+
}
141+
if (extensionsToRemove.length) {
142+
await this.uninstallExtensions(extensionsToRemove);
143+
}
144+
}
145+
146+
private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void {
147+
let locations = this.profileExtensionsLocations.get(key);
148+
if (!locations) {
149+
locations = [];
150+
this.profileExtensionsLocations.set(key, locations);
151+
}
152+
locations.push(extensionsProfileLocation);
153+
}
154+
155+
private removeExtensionWithKey(key: string, profileLocation: URI): boolean {
156+
const profiles = this.profileExtensionsLocations.get(key);
157+
if (profiles) {
158+
const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation));
159+
if (index > -1) {
160+
profiles.splice(index, 1);
161+
}
162+
}
163+
if (!profiles?.length) {
164+
this.profileExtensionsLocations.delete(key);
165+
return true;
166+
}
167+
return false;
168+
}
169+
170+
private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise<void> {
171+
const installed = await this.extensionManagementService.getAllUserInstalled();
172+
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)));
173+
if (toUninstall.length) {
174+
await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions)));
175+
}
176+
}
177+
178+
private getKey(identifier: IExtensionIdentifier, version: string): string {
179+
return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`;
180+
}
181+
182+
private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined {
183+
const [id, version] = getIdAndVersion(key);
184+
return version ? { identifier: { id }, version } : undefined;
185+
}
186+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri';
1414
import { ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
1515
import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp';
1616
import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner';
17+
import { ExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner';
1718
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
1819
import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater';
1920
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
@@ -112,7 +113,6 @@ import { RemoteTunnelService } from 'vs/platform/remoteTunnel/electron-browser/r
112113
import { IRemoteTunnelService } from 'vs/platform/remoteTunnel/common/remoteTunnel';
113114
import { ISharedProcessLifecycleService, SharedProcessLifecycleService } from 'vs/platform/lifecycle/electron-browser/sharedProcessLifecycleService';
114115
import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider';
115-
import { ExtensionsContributions } from 'vs/code/electron-browser/sharedProcess/contrib/extensions';
116116

117117
class SharedProcessMain extends Disposable {
118118

@@ -186,7 +186,7 @@ class SharedProcessMain extends Disposable {
186186
instantiationService.createInstance(UnusedWorkspaceStorageDataCleaner),
187187
instantiationService.createInstance(LogsDataCleaner),
188188
instantiationService.createInstance(LocalizationsUpdater),
189-
instantiationService.createInstance(ExtensionsContributions),
189+
instantiationService.createInstance(ExtensionsCleaner),
190190
instantiationService.createInstance(UserDataProfilesCleaner)
191191
));
192192
}

src/vs/code/electron-main/app.ts

+4
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-m
101101
import { IPolicyService } from 'vs/platform/policy/common/policy';
102102
import { PolicyChannel } from 'vs/platform/policy/common/policyIpc';
103103
import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile';
104+
import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit';
104105
import { RequestChannel } from 'vs/platform/request/common/requestIpc';
105106
import { IRequestService } from 'vs/platform/request/common/request';
106107
import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
@@ -557,6 +558,9 @@ export class CodeApplication extends Disposable {
557558
// Auth Handler
558559
this._register(instantiationService.createInstance(ProxyAuthHandler));
559560

561+
// Default Extensions Profile Init Handler
562+
this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler));
563+
560564
// Transient profiles handler
561565
this._register(instantiationService.createInstance(UserDataTransientProfilesHandler));
562566
}

src/vs/code/node/cliProcessMain.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ class CliMain extends Disposable {
245245
}
246246

247247
private async doRun(environmentService: INativeEnvironmentService, fileService: IFileService, userDataProfilesService: IUserDataProfilesService, instantiationService: IInstantiationService): Promise<void> {
248-
const profileLocation = (environmentService.args.profile ? userDataProfilesService.profiles.find(p => p.name === environmentService.args.profile) ?? userDataProfilesService.defaultProfile : userDataProfilesService.defaultProfile).extensionsResource;
248+
const profileLocation = environmentService.args.profile ? userDataProfilesService.profiles.find(p => p.name === environmentService.args.profile)?.extensionsResource : userDataProfilesService.defaultProfile.extensionsResource;
249249

250250
// Install Source
251251
if (this.argv['install-source']) {

0 commit comments

Comments
 (0)