Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/vs/platform/extensionManagement/node/extensionsScanner.ts')
-rw-r--r--src/vs/platform/extensionManagement/node/extensionsScanner.ts448
1 files changed, 0 insertions, 448 deletions
diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts
deleted file mode 100644
index 6945e43052e..00000000000
--- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts
+++ /dev/null
@@ -1,448 +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 { flatten } from 'vs/base/common/arrays';
-import { Limiter, Promises, Queue } from 'vs/base/common/async';
-import { CancellationToken } from 'vs/base/common/cancellation';
-import { IStringDictionary } from 'vs/base/common/collections';
-import { getErrorMessage } from 'vs/base/common/errors';
-import { Disposable } from 'vs/base/common/lifecycle';
-import { FileAccess } from 'vs/base/common/network';
-import * as path from 'vs/base/common/path';
-import { isWindows } from 'vs/base/common/platform';
-import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources';
-import * as semver from 'vs/base/common/semver/semver';
-import { URI } from 'vs/base/common/uri';
-import { generateUuid } from 'vs/base/common/uuid';
-import * as pfs from 'vs/base/node/pfs';
-import { extract, ExtractError } from 'vs/base/node/zip';
-import { localize } from 'vs/nls';
-import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
-import { ExtensionManagementError, ExtensionManagementErrorCode, Metadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
-import { areSameExtensions, ExtensionKey, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
-import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
-import { ExtensionType, IExtensionIdentifier, ExtensionIdentifier, IExtensionManifest, TargetPlatform, UNDEFINED_PUBLISHER } from 'vs/platform/extensions/common/extensions';
-import { IFileService } from 'vs/platform/files/common/files';
-import { ILogService } from 'vs/platform/log/common/log';
-import { IProductService } from 'vs/platform/product/common/productService';
-
-export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: Metadata };
-type IRelaxedLocalExtension = ILocalExtension & { type: ExtensionType; isBuiltin: boolean; targetPlatform: TargetPlatform };
-
-export class ExtensionsScanner extends Disposable {
-
- private readonly systemExtensionsLocation: URI;
- private readonly userExtensionsLocation: URI;
- private readonly uninstalledPath: string;
- private readonly uninstalledFileLimiter: Queue<any>;
-
- constructor(
- private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
- private readonly targetPlatform: Promise<TargetPlatform>,
- @IFileService private readonly fileService: IFileService,
- @ILogService private readonly logService: ILogService,
- @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
- @IProductService private readonly productService: IProductService,
- ) {
- super();
- this.systemExtensionsLocation = URI.file(environmentService.builtinExtensionsPath);
- this.userExtensionsLocation = URI.file(environmentService.extensionsPath);
- this.uninstalledPath = joinPath(this.userExtensionsLocation, '.obsolete').fsPath;
- this.uninstalledFileLimiter = new Queue();
- }
-
- async cleanUp(): Promise<void> {
- await this.removeUninstalledExtensions();
- await this.removeOutdatedExtensions();
- }
-
- async scanExtensions(type: ExtensionType | null): Promise<ILocalExtension[]> {
- const promises: Promise<ILocalExtension[]>[] = [];
-
- if (type === null || type === ExtensionType.System) {
- promises.push(this.scanDefaultSystemExtensions());
- promises.push(this.environmentService.isBuilt ? Promise.resolve([]) : this.scanDevSystemExtensions());
- } else {
- promises.push(Promise.resolve([]));
- promises.push(Promise.resolve([]));
- }
- promises.push(this.scanUserExtensions(false));
-
- try {
- const [defaultSystemExtensions, devSystemExtensions, userExtensions] = await Promise.all(promises);
- const result = this.dedupExtensions([...defaultSystemExtensions, ...devSystemExtensions, ...userExtensions], await this.targetPlatform);
- return type !== null ? result.filter(r => r.type === type) : result;
- } catch (error) {
- throw this.joinErrors(error);
- }
- }
-
- async scanUserExtensions(excludeOutdated: boolean): Promise<ILocalExtension[]> {
- this.logService.trace('Started scanning user extensions');
- let [uninstalled, extensions] = await Promise.all([this.getUninstalledExtensions(), this.scanFromUserExtensionsLocation()]);
- extensions = extensions.filter(e => !uninstalled[ExtensionKey.create(e).toString()]);
- extensions = excludeOutdated ? this.dedupExtensions(extensions, await this.targetPlatform) : extensions;
- this.logService.trace('Scanned user extensions:', extensions.length);
- return extensions;
- }
-
- async extractUserExtension(extensionKey: ExtensionKey, zipPath: string, metadata: Metadata | undefined, token: CancellationToken): Promise<ILocalExtension> {
- const folderName = extensionKey.toString();
- const tempPath = path.join(this.userExtensionsLocation.fsPath, `.${generateUuid()}`);
- const extensionPath = path.join(this.userExtensionsLocation.fsPath, folderName);
-
- try {
- await pfs.Promises.rm(extensionPath);
- } catch (error) {
- throw new ExtensionManagementError(localize('errorDeleting', "Unable to delete the existing folder '{0}' while installing the extension '{1}'. Please delete the folder manually and try again", extensionPath, extensionKey.id), ExtensionManagementErrorCode.Delete);
- }
-
- await this.extractAtLocation(extensionKey, zipPath, tempPath, token);
- let local = await this.scanExtension(URI.file(tempPath), ExtensionType.User);
- if (!local) {
- throw new Error(localize('cannot read', "Cannot read the extension from {0}", tempPath));
- }
- await this.storeMetadata(local, { ...metadata, installedTimestamp: Date.now() });
-
- try {
- await this.rename(extensionKey, tempPath, extensionPath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
- this.logService.info('Renamed to', extensionPath);
- } catch (error) {
- try {
- await pfs.Promises.rm(tempPath);
- } catch (e) { /* ignore */ }
- if (error.code === 'ENOTEMPTY') {
- this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, extensionKey.id);
- } else {
- this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempPath);
- throw error;
- }
- }
-
- try {
- local = await this.scanExtension(URI.file(extensionPath), ExtensionType.User);
- } catch (e) { /*ignore */ }
-
- if (local) {
- return local;
- }
- throw new Error(localize('cannot read', "Cannot read the extension from {0}", this.userExtensionsLocation.fsPath));
- }
-
- async saveMetadataForLocalExtension(local: ILocalExtension, metadata: Metadata): Promise<ILocalExtension> {
- this.setMetadata(local, metadata);
- await this.storeMetadata(local, { ...metadata, installedTimestamp: local.installedTimestamp });
- return local;
- }
-
- getUninstalledExtensions(): Promise<IStringDictionary<boolean>> {
- return this.withUninstalledExtensions();
- }
-
- 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);
- });
- }
-
- async setInstalled(extensionKey: ExtensionKey): Promise<ILocalExtension | null> {
- await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]);
- const userExtensions = await this.scanUserExtensions(true);
- const localExtension = userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)) || null;
- if (!localExtension) {
- return null;
- }
- await this.storeMetadata(localExtension, { installedTimestamp: Date.now() });
- return this.scanExtension(localExtension.location, localExtension.type);
- }
-
- async removeExtension(extension: ILocalExtension, type: string): Promise<void> {
- this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id, extension.location.fsPath);
- await pfs.Promises.rm(extension.location.fsPath);
- this.logService.info('Deleted from disk', extension.identifier.id, extension.location.fsPath);
- }
-
- async removeUninstalledExtension(extension: ILocalExtension): Promise<void> {
- await this.removeExtension(extension, 'uninstalled');
- await this.withUninstalledExtensions(uninstalled => delete uninstalled[ExtensionKey.create(extension).toString()]);
- }
-
- private async storeMetadata(local: ILocalExtension, metaData: Metadata): Promise<ILocalExtension> {
- // unset if false
- metaData.isMachineScoped = metaData.isMachineScoped || undefined;
- metaData.isBuiltin = metaData.isBuiltin || undefined;
- metaData.installedTimestamp = metaData.installedTimestamp || undefined;
- const manifestPath = path.join(local.location.fsPath, 'package.json');
- const raw = await pfs.Promises.readFile(manifestPath, 'utf8');
- const { manifest } = await this.parseManifest(raw);
- (manifest as ILocalExtensionManifest).__metadata = metaData;
- await pfs.Promises.writeFile(manifestPath, JSON.stringify(manifest, null, '\t'));
- return local;
- }
-
- private async withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary<boolean>) => void): Promise<IStringDictionary<boolean>> {
- return this.uninstalledFileLimiter.queue(async () => {
- let raw: string | undefined;
- try {
- raw = await pfs.Promises.readFile(this.uninstalledPath, 'utf8');
- } catch (err) {
- if (err.code !== 'ENOENT') {
- throw err;
- }
- }
-
- let uninstalled = {};
- if (raw) {
- try {
- uninstalled = JSON.parse(raw);
- } catch (e) { /* ignore */ }
- }
-
- if (updateFn) {
- updateFn(uninstalled);
- if (Object.keys(uninstalled).length) {
- await pfs.Promises.writeFile(this.uninstalledPath, JSON.stringify(uninstalled));
- } else {
- await pfs.Promises.rm(this.uninstalledPath);
- }
- }
-
- return uninstalled;
- });
- }
-
- private async extractAtLocation(identifier: IExtensionIdentifier, zipPath: string, location: string, token: CancellationToken): Promise<void> {
- this.logService.trace(`Started extracting the extension from ${zipPath} to ${location}`);
-
- // Clean the location
- try {
- await pfs.Promises.rm(location);
- } catch (e) {
- throw new ExtensionManagementError(this.joinErrors(e).message, ExtensionManagementErrorCode.Delete);
- }
-
- try {
- await extract(zipPath, location, { sourcePath: 'extension', overwrite: true }, token);
- this.logService.info(`Extracted extension to ${location}:`, identifier.id);
- } catch (e) {
- try { await pfs.Promises.rm(location); } catch (e) { /* Ignore */ }
- let errorCode = ExtensionManagementErrorCode.Extract;
- if (e instanceof ExtractError) {
- if (e.type === 'CorruptZip') {
- errorCode = ExtensionManagementErrorCode.CorruptZip;
- } else if (e.type === 'Incomplete') {
- errorCode = ExtensionManagementErrorCode.IncompleteZip;
- }
- }
- throw new ExtensionManagementError(e.message, errorCode);
- }
- }
-
- private async rename(identifier: IExtensionIdentifier, extractPath: string, renamePath: string, retryUntil: number): Promise<void> {
- try {
- await pfs.Promises.rename(extractPath, renamePath);
- } catch (error) {
- if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
- this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`, identifier.id);
- return this.rename(identifier, extractPath, renamePath, retryUntil);
- }
- throw new ExtensionManagementError(error.message || localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || ExtensionManagementErrorCode.Rename);
- }
- }
-
- private async scanExtensionsInLocation(location: URI, preferredType: ExtensionType): Promise<ILocalExtension[]> {
- const limiter = new Limiter<any>(10);
- const stat = await this.fileService.resolve(location);
- if (stat.children) {
- const extensions = await Promise.all<ILocalExtension>(stat.children.filter(c => c.isDirectory)
- .map(c => limiter.queue(async () => {
- if (isEqualOrParent(c.resource, this.userExtensionsLocation) && basename(c.resource).indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
- return null;
- }
- return this.scanExtension(c.resource, preferredType);
- })));
- return extensions.filter(e => e && e.identifier);
- }
- return [];
- }
-
- private async scanExtension(extensionLocation: URI, preferredType: ExtensionType): Promise<ILocalExtension | null> {
- try {
- const stat = await this.fileService.resolve(extensionLocation);
- if (stat.children) {
- const { manifest, metadata } = await this.readManifest(extensionLocation.fsPath);
- const readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;
- const changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;
- const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
- const type = metadata?.isSystem ? ExtensionType.System : preferredType;
- const local = <ILocalExtension>{ type, identifier, manifest, location: extensionLocation, readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System };
- this.setMetadata(local, metadata);
- return local;
- }
- } catch (e) {
- if (preferredType !== ExtensionType.System) {
- this.logService.trace(e);
- }
- }
- return null;
- }
-
- private async scanDefaultSystemExtensions(): Promise<ILocalExtension[]> {
- this.logService.trace('Started scanning system extensions');
- const result = await this.scanExtensionsInLocation(this.systemExtensionsLocation, ExtensionType.System);
- this.logService.trace('Scanned system extensions:', result.length);
- return result;
- }
-
- private async scanDevSystemExtensions(): Promise<ILocalExtension[]> {
- this.logService.trace('Started scanning dev system extensions');
- const devSystemExtensionsList = this.getDevSystemExtensionsList();
- if (devSystemExtensionsList.length) {
- const result = await this.scanExtensionsInLocation(this.devSystemExtensionsLocation, ExtensionType.System);
- this.logService.trace('Scanned dev system extensions:', result.length);
- return result.filter(r => devSystemExtensionsList.some(id => areSameExtensions(r.identifier, { id })));
- } else {
- return [];
- }
- }
-
- private async scanFromUserExtensionsLocation(): Promise<ILocalExtension[]> {
- return this.scanExtensionsInLocation(this.userExtensionsLocation, ExtensionType.User);
- }
-
- private dedupExtensions(extensions: ILocalExtension[], targetPlatform: TargetPlatform): ILocalExtension[] {
- const result = new Map<string, ILocalExtension>();
- for (const extension of extensions) {
- const extensionKey = ExtensionIdentifier.toKey(extension.identifier.id);
- const existing = result.get(extensionKey);
- if (existing) {
- if (semver.gt(existing.manifest.version, extension.manifest.version)) {
- this.logService.debug(`Skipping extension ${extension.location.fsPath} with lower version ${extension.manifest.version}.`);
- continue;
- }
- if (semver.eq(existing.manifest.version, extension.manifest.version) && existing.targetPlatform === targetPlatform) {
- this.logService.debug(`Skipping extension ${extension.location.fsPath} from different target platform ${extension.targetPlatform}`);
- continue;
- }
- if (existing.type === ExtensionType.System) {
- this.logService.debug(`Overwriting system extension ${existing.location.fsPath} with ${extension.location.fsPath}.`);
- } else {
- this.logService.warn(`Overwriting user extension ${existing.location.fsPath} with ${extension.location.fsPath}.`);
- }
- }
- result.set(extensionKey, extension);
- }
- return [...result.values()];
- }
-
- private setMetadata(local: IRelaxedLocalExtension, metadata: Metadata | null): void {
- local.publisherDisplayName = metadata?.publisherDisplayName || null;
- local.publisherId = metadata?.publisherId || null;
- local.identifier.uuid = metadata?.id;
- local.isMachineScoped = !!metadata?.isMachineScoped;
- local.isPreReleaseVersion = !!metadata?.isPreReleaseVersion;
- local.preRelease = !!metadata?.preRelease;
- local.isBuiltin = local.type === ExtensionType.System || !!metadata?.isBuiltin;
- local.installedTimestamp = metadata?.installedTimestamp;
- local.targetPlatform = metadata?.targetPlatform ?? TargetPlatform.UNDEFINED;
- local.updated = !!metadata?.updated;
- }
-
- private async removeUninstalledExtensions(): Promise<void> {
- const uninstalled = await this.getUninstalledExtensions();
- const extensions = await this.scanFromUserExtensionsLocation(); // All user extensions
- const installed: Set<string> = new Set<string>();
- for (const e of extensions) {
- if (!uninstalled[ExtensionKey.create(e).toString()]) {
- installed.add(e.identifier.id.toLowerCase());
- }
- }
- const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier);
- await Promises.settled(byExtension.map(async e => {
- const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
- if (!installed.has(latest.identifier.id.toLowerCase())) {
- await this.beforeRemovingExtension(latest);
- }
- }));
- const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[ExtensionKey.create(e).toString()]);
- await Promises.settled(toRemove.map(e => this.removeUninstalledExtension(e)));
- }
-
- private async removeOutdatedExtensions(): Promise<void> {
- const extensions = await this.scanFromUserExtensionsLocation();
- const toRemove: ILocalExtension[] = [];
-
- // Outdated extensions
- const targetPlatform = await this.targetPlatform;
- const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => e.identifier);
- toRemove.push(...flatten(byExtension.map(p => p.sort((a, b) => {
- const vcompare = semver.rcompare(a.manifest.version, b.manifest.version);
- if (vcompare !== 0) {
- return vcompare;
- }
- if (a.targetPlatform === targetPlatform) {
- return -1;
- }
- return 1;
- }).slice(1))));
-
- await Promises.settled(toRemove.map(extension => this.removeExtension(extension, 'outdated')));
- }
-
- private getDevSystemExtensionsList(): string[] {
- return (this.productService.builtInExtensions || []).map(e => e.name);
- }
-
- private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
- const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors];
- if (errors.length === 1) {
- return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
- }
- return errors.reduce<Error>((previousValue: Error, currentValue: Error | string) => {
- return new Error(`${previousValue.message}${previousValue.message ? ',' : ''}${currentValue instanceof Error ? currentValue.message : currentValue}`);
- }, new Error(''));
- }
-
- private _devSystemExtensionsLocation: URI | null = null;
- private get devSystemExtensionsLocation(): URI {
- if (!this._devSystemExtensionsLocation) {
- this._devSystemExtensionsLocation = URI.file(path.normalize(path.join(FileAccess.asFileUri('', require).fsPath, '..', '.build', 'builtInExtensions')));
- }
- return this._devSystemExtensionsLocation;
- }
-
- private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: Metadata | null }> {
- const promises = [
- pfs.Promises.readFile(path.join(extensionPath, 'package.json'), 'utf8')
- .then(raw => this.parseManifest(raw)),
- pfs.Promises.readFile(path.join(extensionPath, 'package.nls.json'), 'utf8')
- .then(undefined, err => err.code !== 'ENOENT' ? Promise.reject<string>(err) : '{}')
- .then(raw => JSON.parse(raw))
- ];
-
- const [{ manifest, metadata }, translations] = await Promise.all(promises);
- return {
- manifest: localizeManifest(manifest, translations),
- metadata
- };
- }
-
- private parseManifest(raw: string): Promise<{ manifest: IExtensionManifest; metadata: Metadata | null }> {
- return new Promise((c, e) => {
- try {
- const manifest = <ILocalExtensionManifest & { publisher: string }>JSON.parse(raw);
- // allow publisher to be undefined to make the initial extension authoring experience smoother
- if (!manifest.publisher) {
- manifest.publisher = UNDEFINED_PUBLISHER;
- }
- const metadata = manifest.__metadata || null;
- c({ manifest, metadata });
- } catch (err) {
- e(new Error(localize('invalidManifest', "Extension invalid: package.json is not a JSON file.")));
- }
- });
- }
-}