Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/extensionManagement/common/extensionEnablementService.ts
3296 views
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 { Emitter, Event } from '../../../base/common/event.js';
7
import { Disposable } from '../../../base/common/lifecycle.js';
8
import { isUndefinedOrNull } from '../../../base/common/types.js';
9
import { DISABLED_EXTENSIONS_STORAGE_PATH, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from './extensionManagement.js';
10
import { areSameExtensions } from './extensionManagementUtil.js';
11
import { IProfileStorageValueChangeEvent, IStorageService, StorageScope, StorageTarget } from '../../storage/common/storage.js';
12
13
export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {
14
15
declare readonly _serviceBrand: undefined;
16
17
private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>();
18
readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event;
19
private readonly storageManager: StorageManager;
20
21
constructor(
22
@IStorageService storageService: IStorageService,
23
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
24
) {
25
super();
26
this.storageManager = this._register(new StorageManager(storageService));
27
this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' })));
28
this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => {
29
if (local && operation === InstallOperation.Migrate) {
30
this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */
31
}
32
})));
33
}
34
35
async enableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {
36
if (this._removeFromDisabledExtensions(extension)) {
37
this._onDidChangeEnablement.fire({ extensions: [extension], source });
38
return true;
39
}
40
return false;
41
}
42
43
async disableExtension(extension: IExtensionIdentifier, source?: string): Promise<boolean> {
44
if (this._addToDisabledExtensions(extension)) {
45
this._onDidChangeEnablement.fire({ extensions: [extension], source });
46
return true;
47
}
48
return false;
49
}
50
51
getDisabledExtensions(): IExtensionIdentifier[] {
52
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH);
53
}
54
55
async getDisabledExtensionsAsync(): Promise<IExtensionIdentifier[]> {
56
return this.getDisabledExtensions();
57
}
58
59
private _addToDisabledExtensions(identifier: IExtensionIdentifier): boolean {
60
const disabledExtensions = this.getDisabledExtensions();
61
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
62
disabledExtensions.push(identifier);
63
this._setDisabledExtensions(disabledExtensions);
64
return true;
65
}
66
return false;
67
}
68
69
private _removeFromDisabledExtensions(identifier: IExtensionIdentifier): boolean {
70
const disabledExtensions = this.getDisabledExtensions();
71
for (let index = 0; index < disabledExtensions.length; index++) {
72
const disabledExtension = disabledExtensions[index];
73
if (areSameExtensions(disabledExtension, identifier)) {
74
disabledExtensions.splice(index, 1);
75
this._setDisabledExtensions(disabledExtensions);
76
return true;
77
}
78
}
79
return false;
80
}
81
82
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[]): void {
83
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions);
84
}
85
86
private _getExtensions(storageId: string): IExtensionIdentifier[] {
87
return this.storageManager.get(storageId, StorageScope.PROFILE);
88
}
89
90
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void {
91
this.storageManager.set(storageId, extensions, StorageScope.PROFILE);
92
}
93
94
}
95
96
export class StorageManager extends Disposable {
97
98
private storage: { [key: string]: string } = Object.create(null);
99
100
private _onDidChange: Emitter<IExtensionIdentifier[]> = this._register(new Emitter<IExtensionIdentifier[]>());
101
readonly onDidChange: Event<IExtensionIdentifier[]> = this._onDidChange.event;
102
103
constructor(private storageService: IStorageService) {
104
super();
105
this._register(storageService.onDidChangeValue(StorageScope.PROFILE, undefined, this._store)(e => this.onDidStorageChange(e)));
106
}
107
108
get(key: string, scope: StorageScope): IExtensionIdentifier[] {
109
let value: string;
110
if (scope === StorageScope.PROFILE) {
111
if (isUndefinedOrNull(this.storage[key])) {
112
this.storage[key] = this._get(key, scope);
113
}
114
value = this.storage[key];
115
} else {
116
value = this._get(key, scope);
117
}
118
return JSON.parse(value);
119
}
120
121
set(key: string, value: IExtensionIdentifier[], scope: StorageScope): void {
122
const newValue: string = JSON.stringify(value.map(({ id, uuid }): IExtensionIdentifier => ({ id, uuid })));
123
const oldValue = this._get(key, scope);
124
if (oldValue !== newValue) {
125
if (scope === StorageScope.PROFILE) {
126
if (value.length) {
127
this.storage[key] = newValue;
128
} else {
129
delete this.storage[key];
130
}
131
}
132
this._set(key, value.length ? newValue : undefined, scope);
133
}
134
}
135
136
private onDidStorageChange(storageChangeEvent: IProfileStorageValueChangeEvent): void {
137
if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) {
138
const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope);
139
if (newValue !== this.storage[storageChangeEvent.key]) {
140
const oldValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
141
delete this.storage[storageChangeEvent.key];
142
const newValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
143
const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue)));
144
const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue)));
145
if (added.length || removed.length) {
146
this._onDidChange.fire([...added, ...removed]);
147
}
148
}
149
}
150
}
151
152
private _get(key: string, scope: StorageScope): string {
153
return this.storageService.get(key, scope, '[]');
154
}
155
156
private _set(key: string, value: string | undefined, scope: StorageScope): void {
157
if (value) {
158
// Enablement state is synced separately through extensions
159
this.storageService.store(key, value, scope, StorageTarget.MACHINE);
160
} else {
161
this.storageService.remove(key, scope);
162
}
163
}
164
}
165
166