Path: blob/main/src/vs/platform/extensionManagement/common/extensionEnablementService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../base/common/event.js';6import { Disposable } from '../../../base/common/lifecycle.js';7import { isUndefinedOrNull } from '../../../base/common/types.js';8import { DISABLED_EXTENSIONS_STORAGE_PATH, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from './extensionManagement.js';9import { areSameExtensions } from './extensionManagementUtil.js';10import { IProfileStorageValueChangeEvent, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';1112export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {1314declare readonly _serviceBrand: undefined;1516private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>();17readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event;18private readonly storageManager: StorageManager;1920constructor(21@IStorageService storageService: IStorageService,22@IExtensionManagementService extensionManagementService: IExtensionManagementService,23) {24super();25this.storageManager = this._register(new StorageManager(storageService));26this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' })));27this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => {28if (local && operation === InstallOperation.Migrate) {29this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */30}31})));32}3334async enableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {35if (this._removeFromDisabledExtensions(extension)) {36this._onDidChangeEnablement.fire({ extensions: [extension], source });37return true;38}39return false;40}4142async disableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {43if (this._addToDisabledExtensions(extension)) {44this._onDidChangeEnablement.fire({ extensions: [extension], source });45return true;46}47return false;48}4950getDisabledExtensions(): IExtensionIdentifier[] {51return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);52}5354async getDisabledExtensionsAsync(): Promise<IExtensionIdentifier[]> {55return this.getDisabledExtensions();56}5758private _addToDisabledExtensions(identifier: IExtensionIdentifier): boolean {59const disabledExtensions = this.getDisabledExtensions();60if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {61disabledExtensions.push(identifier);62this._setDisabledExtensions(disabledExtensions);63return true;64}65return false;66}6768private _removeFromDisabledExtensions(identifier: IExtensionIdentifier): boolean {69const disabledExtensions = this.getDisabledExtensions();70for (let index = 0; index < disabledExtensions.length; index++) {71const disabledExtension = disabledExtensions[index];72if (areSameExtensions(disabledExtension, identifier)) {73disabledExtensions.splice(index, 1);74this._setDisabledExtensions(disabledExtensions);75return true;76}77}78return false;79}8081private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {82this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);83}8485private _getExtensions(storageId: string): IExtensionIdentifier[] {86return this.storageManager.get(storageId, StorageScope.PROFILE);87}8889private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {90this.storageManager.set(storageId, extensions, StorageScope.PROFILE);91}9293}9495export class StorageManager extends Disposable {9697private storage: { [key: string]: string } = Object.create(null);9899private _onDidChange: Emitter<IExtensionIdentifier[]> = this._register(new Emitter<IExtensionIdentifier[]>());100readonly onDidChange: Event<IExtensionIdentifier[]> = this._onDidChange.event;101102constructor(private storageService: IStorageService) {103super();104this._register(storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this._store)(e => this.onDidStorageChange(e)));105}106107get(key: string, scope: StorageScope): IExtensionIdentifier[] {108let value: string;109if (scope === StorageScope.PROFILE) {110if (isUndefinedOrNull(this.storage[key])) {111this.storage[key] = this._get(key, scope);112}113value = this.storage[key];114} else {115value = this._get(key, scope);116}117return JSON.parse(value);118}119120set(key: string, value: IExtensionIdentifier[], scope: StorageScope): void {121const newValue: string = JSON.stringify(value.map(({ id, uuid }): IExtensionIdentifier => ({ id, uuid })));122const oldValue = this._get(key, scope);123if (oldValue !== newValue) {124if (scope === StorageScope.PROFILE) {125if (value.length) {126this.storage[key] = newValue;127} else {128delete this.storage[key];129}130}131this._set(key, value.length ? newValue : undefined, scope);132}133}134135private onDidStorageChange(storageChangeEvent: IProfileStorageValueChangeEvent): void {136if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) {137const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope);138if (newValue !== this.storage[storageChangeEvent.key]) {139const oldValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);140delete this.storage[storageChangeEvent.key];141const newValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);142const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue)));143const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue)));144if (added.length || removed.length) {145this._onDidChange.fire([...added, ...removed]);146}147}148}149}150151private _get(key: string, scope: StorageScope): string {152return this.storageService.get(key, scope, '[]');153}154155private _set(key: string, value: string | undefined, scope: StorageScope): void {156if (value) {157// Enablement state is synced separately through extensions158this.storageService.store(key, value, scope, StorageTarget.MACHINE);159} else {160this.storageService.remove(key, scope);161}162}163}164165166