UNPKG

sussudio

Version:

An unofficial VS Code Internal API

325 lines (324 loc) 16.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import { Queue } from "../../../base/common/async.mjs"; import { VSBuffer } from "../../../base/common/buffer.mjs"; import { Disposable } from "../../../base/common/lifecycle.mjs"; import { Emitter } from "../../../base/common/event.mjs"; import { ResourceMap } from "../../../base/common/map.mjs"; import { URI } from "../../../base/common/uri.mjs"; import { areSameExtensions } from "./extensionManagementUtil.mjs"; import { isIExtensionIdentifier } from "../../extensions/common/extensions.mjs"; import { IFileService, toFileOperationResult } from "../../files/common/files.mjs"; import { createDecorator } from "../../instantiation/common/instantiation.mjs"; import { ILogService } from "../../log/common/log.mjs"; import { IUserDataProfilesService } from "../../userDataProfile/common/userDataProfile.mjs"; import { IUriIdentityService } from "../../uriIdentity/common/uriIdentity.mjs"; import { isObject, isString } from "../../../base/common/types.mjs"; import { getErrorMessage } from "../../../base/common/errors.mjs"; import { ITelemetryService } from "../../telemetry/common/telemetry.mjs"; export var ExtensionsProfileScanningErrorCode; (function (ExtensionsProfileScanningErrorCode) { /** * Error when trying to scan extensions from a profile that does not exist. */ ExtensionsProfileScanningErrorCode["ERROR_PROFILE_NOT_FOUND"] = "ERROR_PROFILE_NOT_FOUND"; /** * Error when profile file is invalid. */ ExtensionsProfileScanningErrorCode["ERROR_INVALID_CONTENT"] = "ERROR_INVALID_CONTENT"; })(ExtensionsProfileScanningErrorCode || (ExtensionsProfileScanningErrorCode = {})); export class ExtensionsProfileScanningError extends Error { code; constructor(message, code) { super(message); this.code = code; } } export const IExtensionsProfileScannerService = createDecorator('IExtensionsProfileScannerService'); let AbstractExtensionsProfileScannerService = class AbstractExtensionsProfileScannerService extends Disposable { extensionsLocation; fileService; userDataProfilesService; uriIdentityService; telemetryService; logService; _serviceBrand; _onAddExtensions = this._register(new Emitter()); onAddExtensions = this._onAddExtensions.event; _onDidAddExtensions = this._register(new Emitter()); onDidAddExtensions = this._onDidAddExtensions.event; _onRemoveExtensions = this._register(new Emitter()); onRemoveExtensions = this._onRemoveExtensions.event; _onDidRemoveExtensions = this._register(new Emitter()); onDidRemoveExtensions = this._onDidRemoveExtensions.event; resourcesAccessQueueMap = new ResourceMap(); constructor(extensionsLocation, fileService, userDataProfilesService, uriIdentityService, telemetryService, logService) { super(); this.extensionsLocation = extensionsLocation; this.fileService = fileService; this.userDataProfilesService = userDataProfilesService; this.uriIdentityService = uriIdentityService; this.telemetryService = telemetryService; this.logService = logService; } scanProfileExtensions(profileLocation, options) { return this.withProfileExtensions(profileLocation, undefined, options); } async addExtensionsToProfile(extensions, profileLocation) { const extensionsToRemove = []; const extensionsToAdd = []; try { await this.withProfileExtensions(profileLocation, profileExtensions => { const result = []; for (const extension of profileExtensions) { if (extensions.some(([e]) => areSameExtensions(e.identifier, extension.identifier) && e.manifest.version !== extension.version)) { // Remove the existing extension with different version extensionsToRemove.push(extension); } else { result.push(extension); } } for (const [extension, metadata] of extensions) { if (!result.some(e => areSameExtensions(e.identifier, extension.identifier) && e.version === extension.manifest.version)) { // Add only if the same version of the extension is not already added const extensionToAdd = { identifier: extension.identifier, version: extension.manifest.version, location: extension.location, metadata }; extensionsToAdd.push(extensionToAdd); result.push(extensionToAdd); } } if (extensionsToAdd.length) { this._onAddExtensions.fire({ extensions: extensionsToAdd, profileLocation }); } if (extensionsToRemove.length) { this._onRemoveExtensions.fire({ extensions: extensionsToRemove, profileLocation }); } return result; }); if (extensionsToAdd.length) { this._onDidAddExtensions.fire({ extensions: extensionsToAdd, profileLocation }); } if (extensionsToRemove.length) { this._onDidRemoveExtensions.fire({ extensions: extensionsToRemove, profileLocation }); } return extensionsToAdd; } catch (error) { if (extensionsToAdd.length) { this._onDidAddExtensions.fire({ extensions: extensionsToAdd, error, profileLocation }); } if (extensionsToRemove.length) { this._onDidRemoveExtensions.fire({ extensions: extensionsToRemove, error, profileLocation }); } throw error; } } async removeExtensionFromProfile(extension, profileLocation) { const extensionsToRemove = []; this._onRemoveExtensions.fire({ extensions: extensionsToRemove, profileLocation }); try { await this.withProfileExtensions(profileLocation, profileExtensions => { const result = []; for (const e of profileExtensions) { if (areSameExtensions(e.identifier, extension.identifier)) { extensionsToRemove.push(e); } else { result.push(e); } } if (extensionsToRemove.length) { this._onRemoveExtensions.fire({ extensions: extensionsToRemove, profileLocation }); } return result; }); if (extensionsToRemove.length) { this._onDidRemoveExtensions.fire({ extensions: extensionsToRemove, profileLocation }); } } catch (error) { if (extensionsToRemove.length) { this._onDidRemoveExtensions.fire({ extensions: extensionsToRemove, error, profileLocation }); } throw error; } } async withProfileExtensions(file, updateFn, options) { return this.getResourceAccessQueue(file).queue(async () => { let extensions = []; // Read let storedProfileExtensions; try { const content = await this.fileService.readFile(file); storedProfileExtensions = JSON.parse(content.value.toString()); } catch (error) { if (toFileOperationResult(error) !== 1 /* FileOperationResult.FILE_NOT_FOUND */) { throw error; } // migrate from old location, remove this after couple of releases if (this.uriIdentityService.extUri.isEqual(file, this.userDataProfilesService.defaultProfile.extensionsResource)) { storedProfileExtensions = await this.migrateFromOldDefaultProfileExtensionsLocation(); } if (!storedProfileExtensions && options?.bailOutWhenFileNotFound) { throw new ExtensionsProfileScanningError(getErrorMessage(error), "ERROR_PROFILE_NOT_FOUND" /* ExtensionsProfileScanningErrorCode.ERROR_PROFILE_NOT_FOUND */); } } if (storedProfileExtensions) { if (!Array.isArray(storedProfileExtensions)) { this.reportAndThrowInvalidConentError(file); } // TODO @sandy081: Remove this migration after couple of releases let migrate = false; for (const e of storedProfileExtensions) { if (!isStoredProfileExtension(e)) { this.reportAndThrowInvalidConentError(file); } let location; if (isString(e.location)) { location = this.resolveExtensionLocation(e.location); } else { location = URI.revive(e.location); const relativePath = this.toRelativePath(location); if (relativePath) { migrate = true; e.location = relativePath; } } extensions.push({ identifier: e.identifier, location, version: e.version, metadata: e.metadata, }); } if (migrate) { await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedProfileExtensions))); } } // Update if (updateFn) { extensions = updateFn(extensions); const storedProfileExtensions = extensions.map(e => ({ identifier: e.identifier, version: e.version, location: this.toRelativePath(e.location) ?? e.location.toJSON(), metadata: e.metadata })); await this.fileService.writeFile(file, VSBuffer.fromString(JSON.stringify(storedProfileExtensions))); } return extensions; }); } reportAndThrowInvalidConentError(file) { const error = new ExtensionsProfileScanningError(`Invalid extensions content in ${file.toString()}`, "ERROR_INVALID_CONTENT" /* ExtensionsProfileScanningErrorCode.ERROR_INVALID_CONTENT */); this.telemetryService.publicLogError2('extensionsProfileScanningError', { code: error.code }); throw error; } toRelativePath(extensionLocation) { return this.uriIdentityService.extUri.isEqualOrParent(extensionLocation, this.extensionsLocation) ? this.uriIdentityService.extUri.relativePath(this.extensionsLocation, extensionLocation) : undefined; } resolveExtensionLocation(path) { return this.uriIdentityService.extUri.joinPath(this.extensionsLocation, path); } _migrationPromise; async migrateFromOldDefaultProfileExtensionsLocation() { if (!this._migrationPromise) { this._migrationPromise = (async () => { const oldDefaultProfileExtensionsLocation = this.uriIdentityService.extUri.joinPath(this.userDataProfilesService.defaultProfile.location, 'extensions.json'); let content; try { content = (await this.fileService.readFile(oldDefaultProfileExtensionsLocation)).value.toString(); } catch (error) { if (toFileOperationResult(error) === 1 /* FileOperationResult.FILE_NOT_FOUND */) { return undefined; } throw error; } this.logService.info('Migrating extensions from old default profile location', oldDefaultProfileExtensionsLocation.toString()); let storedProfileExtensions; try { const parsedData = JSON.parse(content); if (Array.isArray(parsedData) && parsedData.every(candidate => isStoredProfileExtension(candidate))) { storedProfileExtensions = parsedData; } else { this.logService.warn('Skipping migrating from old default profile locaiton: Found invalid data', parsedData); } } catch (error) { /* Ignore */ this.logService.error(error); } if (storedProfileExtensions) { try { await this.fileService.createFile(this.userDataProfilesService.defaultProfile.extensionsResource, VSBuffer.fromString(JSON.stringify(storedProfileExtensions)), { overwrite: false }); this.logService.info('Migrated extensions from old default profile location to new location', oldDefaultProfileExtensionsLocation.toString(), this.userDataProfilesService.defaultProfile.extensionsResource.toString()); } catch (error) { if (toFileOperationResult(error) === 3 /* FileOperationResult.FILE_MODIFIED_SINCE */) { this.logService.info('Migration from old default profile location to new location is done by another window', oldDefaultProfileExtensionsLocation.toString(), this.userDataProfilesService.defaultProfile.extensionsResource.toString()); } else { throw error; } } } try { await this.fileService.del(oldDefaultProfileExtensionsLocation); } catch (error) { if (toFileOperationResult(error) !== 1 /* FileOperationResult.FILE_NOT_FOUND */) { this.logService.error(error); } } return storedProfileExtensions; })(); } return this._migrationPromise; } getResourceAccessQueue(file) { let resourceQueue = this.resourcesAccessQueueMap.get(file); if (!resourceQueue) { resourceQueue = new Queue(); this.resourcesAccessQueueMap.set(file, resourceQueue); } return resourceQueue; } }; AbstractExtensionsProfileScannerService = __decorate([ __param(1, IFileService), __param(2, IUserDataProfilesService), __param(3, IUriIdentityService), __param(4, ITelemetryService), __param(5, ILogService) ], AbstractExtensionsProfileScannerService); export { AbstractExtensionsProfileScannerService }; function isStoredProfileExtension(candidate) { return isObject(candidate) && isIExtensionIdentifier(candidate.identifier) && (isUriComponents(candidate.location) || isString(candidate.location)) && candidate.version && isString(candidate.version); } function isUriComponents(thing) { if (!thing) { return false; } return isString(thing.path) && isString(thing.scheme); }