Skip to content

Commit 23b25e9

Browse files
authored
- store default extensions manifest in extensions dir - command to install an extension from location - remember and init default profile extensions in cached scanner
1 parent a9f0251 commit 23b25e9

File tree

22 files changed

+238
-100
lines changed

22 files changed

+238
-100
lines changed

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Disposable } from 'vs/base/common/lifecycle';
7-
import { DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
7+
import { IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
88
import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
99
import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration';
1010
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
1111
import { ILogService } from 'vs/platform/log/common/log';
12-
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
12+
import { IStorageService } from 'vs/platform/storage/common/storage';
1313

1414
export class ExtensionsContributions extends Disposable {
1515
constructor(
@@ -22,9 +22,6 @@ export class ExtensionsContributions extends Disposable {
2222
) {
2323
super();
2424

25-
extensionManagementService.migrateDefaultProfileExtensions()
26-
.then(() => storageService.store(DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE), error => null);
27-
2825
extensionManagementService.removeUninstalledExtensions();
2926
migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService);
3027
ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
651651
abstract unzip(zipLocation: URI): Promise<IExtensionIdentifier>;
652652
abstract getManifest(vsix: URI): Promise<IExtensionManifest>;
653653
abstract install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
654+
abstract installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;
654655
abstract getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;
655656
abstract download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI>;
656657
abstract reinstallFromGallery(extension: ILocalExtension): Promise<void>;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
1616
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9-A-Z]*)\\.([a-z0-9A-Z][a-z0-9-A-Z]*)$';
1717
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
1818
export const WEB_EXTENSION_TAG = '__web_extension';
19-
export const DEFAULT_PROFILE_EXTENSIONS_MIGRATION_KEY = 'DEFAULT_PROFILE_EXTENSIONS_MIGRATION';
2019

2120
export function TargetPlatformToString(targetPlatform: TargetPlatform) {
2221
switch (targetPlatform) {
@@ -439,6 +438,7 @@ export interface IExtensionManagementService {
439438
install(vsix: URI, options?: InstallVSIXOptions): Promise<ILocalExtension>;
440439
canInstall(extension: IGalleryExtension): Promise<boolean>;
441440
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
441+
installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension>;
442442
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
443443
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
444444
getInstalled(type?: ExtensionType, profileLocation?: URI): Promise<ILocalExtension[]>;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export class ExtensionManagementChannel implements IServerChannel {
6464
case 'zip': return this.service.zip(transformIncomingExtension(args[0], uriTransformer)).then(uri => transformOutgoingURI(uri, uriTransformer));
6565
case 'unzip': return this.service.unzip(transformIncomingURI(args[0], uriTransformer));
6666
case 'install': return this.service.install(transformIncomingURI(args[0], uriTransformer), revive(args[1]));
67+
case 'installFromLocation': return this.service.installFromLocation(transformIncomingURI(args[0], uriTransformer), URI.revive(args[1]));
6768
case 'getManifest': return this.service.getManifest(transformIncomingURI(args[0], uriTransformer));
6869
case 'getTargetPlatform': return this.service.getTargetPlatform();
6970
case 'canInstall': return this.service.canInstall(args[0]);
@@ -139,6 +140,10 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
139140
return Promise.resolve(this.channel.call<ILocalExtension>('install', [vsix, options])).then(local => transformIncomingExtension(local, null));
140141
}
141142

143+
installFromLocation(location: URI, profileLocation: URI): Promise<ILocalExtension> {
144+
return Promise.resolve(this.channel.call<ILocalExtension>('installFromLocation', [location, profileLocation])).then(local => transformIncomingExtension(local, null));
145+
}
146+
142147
getManifest(vsix: URI): Promise<IExtensionManifest> {
143148
return Promise.resolve(this.channel.call<IExtensionManifest>('getManifest', [vsix]));
144149
}

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

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import { ResourceMap } from 'vs/base/common/map';
1111
import { URI, UriComponents } from 'vs/base/common/uri';
1212
import { Metadata } from 'vs/platform/extensionManagement/common/extensionManagement';
1313
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
14-
import { IExtension, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
15-
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
14+
import { IExtension, IExtensionIdentifier, isIExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
15+
import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files';
1616
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
1717
import { ILogService } from 'vs/platform/log/common/log';
18+
import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
19+
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
1820

1921
interface IStoredProfileExtension {
2022
identifier: IExtensionIdentifier;
@@ -76,6 +78,8 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
7678

7779
constructor(
7880
@IFileService private readonly fileService: IFileService,
81+
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
82+
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
7983
@ILogService private readonly logService: ILogService,
8084
) {
8185
super();
@@ -167,9 +171,20 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
167171
let extensions: IScannedProfileExtension[] = [];
168172

169173
// Read
174+
let storedWebExtensions: IStoredProfileExtension[] | undefined;
170175
try {
171176
const content = await this.fileService.readFile(file);
172-
const storedWebExtensions: IStoredProfileExtension[] = JSON.parse(content.value.toString());
177+
storedWebExtensions = <IStoredProfileExtension[]>JSON.parse(content.value.toString());
178+
} catch (error) {
179+
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
180+
throw error;
181+
}
182+
// migrate from old location, remove this after couple of releases
183+
if (this.uriIdentityService.extUri.isEqual(file, this.userDataProfilesService.defaultProfile.extensionsResource)) {
184+
storedWebExtensions = await this.migrateFromOldDefaultProfileExtensionsLocation();
185+
}
186+
}
187+
if (storedWebExtensions) {
173188
for (const e of storedWebExtensions) {
174189
if (!e.identifier) {
175190
this.logService.info('Ignoring invalid extension while scanning. Identifier does not exist.', e);
@@ -190,11 +205,6 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
190205
metadata: e.metadata,
191206
});
192207
}
193-
} catch (error) {
194-
/* Ignore */
195-
if ((<FileOperationError>error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) {
196-
this.logService.error(error);
197-
}
198208
}
199209

200210
// Update
@@ -213,6 +223,66 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
213223
});
214224
}
215225

226+
private _migrationPromise: Promise<IStoredProfileExtension[] | undefined> | undefined;
227+
private async migrateFromOldDefaultProfileExtensionsLocation(): Promise<IStoredProfileExtension[] | undefined> {
228+
if (!this._migrationPromise) {
229+
this._migrationPromise = (async () => {
230+
const oldDefaultProfileExtensionsLocation = this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.location, 'extensions.json');
231+
let content: string;
232+
try {
233+
content = (await this.fileService.readFile(oldDefaultProfileExtensionsLocation)).value.toString();
234+
} catch (error) {
235+
if (toFileOperationResult(error) === FileOperationResult.FILE_NOT_FOUND) {
236+
return undefined;
237+
}
238+
throw error;
239+
}
240+
241+
this.logService.info('Migrating extensions from old default profile location', oldDefaultProfileExtensionsLocation.toString());
242+
let storedProfileExtensions: IStoredProfileExtension[] | undefined;
243+
try {
244+
const parsedData = JSON.parse(content);
245+
if (Array.isArray(parsedData) && parsedData.every(candidate =>
246+
!!(candidate && typeof candidate === 'object'
247+
&& isIExtensionIdentifier(candidate.identifier)
248+
&& isUriComponents(candidate.location)
249+
&& candidate.version && typeof candidate.version === 'string'))) {
250+
storedProfileExtensions = parsedData;
251+
} else {
252+
this.logService.warn('Skipping migrating from old default profile locaiton: Found invalid data', parsedData);
253+
}
254+
} catch (error) {
255+
/* Ignore */
256+
this.logService.error(error);
257+
}
258+
259+
if (storedProfileExtensions) {
260+
try {
261+
await this.fileService.createFile(this.userDataProfilesService.defaultProfile.extensionsResource, VSBuffer.fromString(JSON.stringify(storedProfileExtensions)), { overwrite: false });
262+
this.logService.info('Migrated extensions from old default profile location to new location', oldDefaultProfileExtensionsLocation.toString(), this.userDataProfilesService.defaultProfile.extensionsResource.toString());
263+
} catch (error) {
264+
if (toFileOperationResult(error) === FileOperationResult.FILE_MODIFIED_SINCE) {
265+
this.logService.info('Migration from old default profile location to new location is done by another window', oldDefaultProfileExtensionsLocation.toString(), this.userDataProfilesService.defaultProfile.extensionsResource.toString());
266+
} else {
267+
throw error;
268+
}
269+
}
270+
}
271+
272+
try {
273+
await this.fileService.del(oldDefaultProfileExtensionsLocation);
274+
} catch (error) {
275+
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
276+
this.logService.error(error);
277+
}
278+
}
279+
280+
return storedProfileExtensions;
281+
})();
282+
}
283+
return this._migrationPromise;
284+
}
285+
216286
private getResourceAccessQueue(file: URI): Queue<IScannedProfileExtension[]> {
217287
let resourceQueue = this.resourcesAccessQueueMap.get(file);
218288
if (!resourceQueue) {
@@ -222,3 +292,11 @@ export class ExtensionsProfileScannerService extends Disposable implements IExte
222292
return resourceQueue;
223293
}
224294
}
295+
296+
function isUriComponents(thing: unknown): thing is UriComponents {
297+
if (!thing) {
298+
return false;
299+
}
300+
return typeof (<any>thing).path === 'string' &&
301+
typeof (<any>thing).scheme === 'string';
302+
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export interface IExtensionsScannerService {
130130

131131
scanMetadata(extensionLocation: URI): Promise<Metadata | undefined>;
132132
updateMetadata(extensionLocation: URI, metadata: Partial<Metadata>): Promise<void>;
133+
initializeDefaultProfileExtensions(): Promise<void>;
133134
}
134135

135136
export abstract class AbstractExtensionsScannerService extends Disposable implements IExtensionsScannerService {
@@ -260,6 +261,42 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
260261
await this.fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t')));
261262
}
262263

264+
private _initializeDefaultProfileExtensionsPromise: Promise<void> | undefined = undefined;
265+
initializeDefaultProfileExtensions(): Promise<void> {
266+
if (!this._initializeDefaultProfileExtensionsPromise) {
267+
this._initializeDefaultProfileExtensionsPromise = (async () => {
268+
try {
269+
const initMarker = this.uriIdentityService.extUri.joinPath(this.userExtensionsLocation, '.init-default-profile-extensions');
270+
if (await this.fileService.exists(initMarker)) {
271+
return;
272+
}
273+
if (
274+
// current default profile extensions location
275+
!(await this.fileService.exists(this.userDataProfilesService.defaultProfile.extensionsResource))
276+
// old default profile extensions location
277+
&& !(await this.fileService.exists(this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.location, 'extensions.json')))
278+
) {
279+
this.logService.info('Started initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString());
280+
const userExtensions = await this.scanUserExtensions({ includeInvalid: true });
281+
await this.extensionsProfileScannerService.addExtensionsToProfile(userExtensions.map(e => [e, e.metadata]), this.userDataProfilesService.defaultProfile.extensionsResource);
282+
this.logService.info('Completed initializing default profile extensions in extensions installation folder.', this.userExtensionsLocation.toString());
283+
}
284+
try {
285+
await this.fileService.createFile(initMarker, VSBuffer.fromString(''));
286+
} catch (error) {
287+
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
288+
this.logService.warn('Failed to create default profile extensions initialize marker in extensions installation folder.', this.userExtensionsLocation.toString(), getErrorMessage(error));
289+
}
290+
}
291+
} catch (error) {
292+
this.logService.error(error);
293+
throw error;
294+
}
295+
})();
296+
}
297+
return this._initializeDefaultProfileExtensionsPromise;
298+
}
299+
263300
private async applyScanOptions(extensions: IRelaxedScannedExtension[], type: ExtensionType | 'development', scanOptions: ScanOptions, pickLatest: boolean): Promise<IRelaxedScannedExtension[]> {
264301
if (!scanOptions.includeAllVersions) {
265302
extensions = this.dedupExtensions(type === ExtensionType.System ? extensions : undefined, type === ExtensionType.User ? extensions : undefined, type === 'development' ? extensions : undefined, await this.getTargetPlatform(), pickLatest);

0 commit comments

Comments
 (0)