storage.ts ×41

Frontier kind: Code frontier

unlabeled · c_32372e37b676

1813 tests · 12188 LOC · 50 files · introduces 0 tests · 663 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
74 ranges663 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1682 ranges12188 lines · 50 files · Browse complete extent
All tests (intent)
1813 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 663 introduced LOC across 74 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/storage/common/storage.ts 451 introduced LOC · 41 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- storage.ts
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 { Promises, RunOnceScheduler, runWhenGlobalIdle } from '../../../base/common/async.js';
7 > import { Emitter, Event, PauseableEmitter } from '../../../base/common/event.js';
8 > import { Disposable, DisposableStore, dispose, MutableDisposable } from '../../../base/common/lifecycle.js';
9 > import { mark } from '../../../base/common/performance.js';
10 > import { isUndefinedOrNull } from '../../../base/common/types.js';
11 > import { InMemoryStorageDatabase, IStorage, IStorageChangeEvent, Storage, StorageHint, StorageValue } from '../../../base/parts/storage/common/storage.js';
12 > import { createDecorator } from '../../instantiation/common/instantiation.js';
13 > import { isUserDataProfile, IUserDataProfile } from '../../userDataProfile/common/userDataProfile.js';
14 > import { IAnyWorkspaceIdentifier } from '../../workspace/common/workspace.js';
15 >
16 > export const IS_NEW_KEY = '__$__isNewStorageMarker';
17 > export const TARGET_KEY = '__$__targetStorageMarker';
18 >
19 > export const IStorageService = createDecorator<IStorageService>('storageService');
20 >
21 > export enum WillSaveStateReason {
22 >
23 > /**
24 > * No specific reason to save state.
25 > */
26 > NONE,
27 >
28 > /**
29 > * A hint that the workbench is about to shutdown.
30 > */
31 > SHUTDOWN
32 > }
33 >
34 > export interface IWillSaveStateEvent {
35 > readonly reason: WillSaveStateReason;
36 > }
37 >
38 > export interface IStorageEntry {
39 > readonly key: string;
40 > readonly value: StorageValue;
41 > readonly scope: StorageScope;
42 > readonly target: StorageTarget;
43 > }
44 >
45 > export interface IWorkspaceStorageValueChangeEvent extends IStorageValueChangeEvent {
46 > readonly scope: StorageScope.WORKSPACE;
47 > }
48 >
49 > export interface IProfileStorageValueChangeEvent extends IStorageValueChangeEvent {
50 > readonly scope: StorageScope.PROFILE;
51 > }
52 >
53 > export interface IApplicationStorageValueChangeEvent extends IStorageValueChangeEvent {
54 > readonly scope: StorageScope.APPLICATION;
55 > }
56 >
57 > export interface IApplicationSharedStorageValueChangeEvent extends IStorageValueChangeEvent {
58 > readonly scope: StorageScope.APPLICATION_SHARED;
59 > }
60 >
61 > export interface IStorageService {
62 >
63 > readonly _serviceBrand: undefined;
64 >
65 > /**
66 > * Emitted whenever data is updated or deleted on the given
67 > * scope and optional key.
68 > *
69 > * @param scope the `StorageScope` to listen to changes
70 > * @param key the optional key to filter for or all keys of
71 > * the scope if `undefined`
72 > */
73 > onDidChangeValue(scope: StorageScope.WORKSPACE, key: string | undefined, disposable: DisposableStore): Event<IWorkspaceStorageValueChangeEvent>;
74 > onDidChangeValue(scope: StorageScope.PROFILE, key: string | undefined, disposable: DisposableStore): Event<IProfileStorageValueChangeEvent>;
75 > onDidChangeValue(scope: StorageScope.APPLICATION, key: string | undefined, disposable: DisposableStore): Event<IApplicationStorageValueChangeEvent>;
76 > onDidChangeValue(scope: StorageScope.APPLICATION_SHARED, key: string | undefined, disposable: DisposableStore): Event<IApplicationSharedStorageValueChangeEvent>;
77 > onDidChangeValue(scope: StorageScope, key: string | undefined, disposable: DisposableStore): Event<IStorageValueChangeEvent>;
78 >
79 > /**
80 > * Emitted whenever target of a storage entry changes.
81 > */
82 > readonly onDidChangeTarget: Event<IStorageTargetChangeEvent>;
83 >
84 > /**
85 > * Emitted when the storage is about to persist. This is the right time
86 > * to persist data to ensure it is stored before the application shuts
87 > * down.
88 > *
89 > * The will save state event allows to optionally ask for the reason of
90 > * saving the state, e.g. to find out if the state is saved due to a
91 > * shutdown.
92 > *
93 > * Note: this event may be fired many times, not only on shutdown to prevent
94 > * loss of state in situations where the shutdown is not sufficient to
95 > * persist the data properly.
96 > */
97 > readonly onWillSaveState: Event<IWillSaveStateEvent>;
98 >
99 > /**
100 > * Retrieve an element stored with the given key from storage. Use
101 > * the provided `defaultValue` if the element is `null` or `undefined`.
102 > *
103 > * @param scope allows to define the scope of the storage operation
104 > * to either the current workspace only, all workspaces or all profiles.
105 > */
106 > get(key: string, scope: StorageScope, fallbackValue: string): string;
107 > get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
108 >
109 > /**
110 > * Retrieve an element stored with the given key from storage. Use
111 > * the provided `defaultValue` if the element is `null` or `undefined`.
112 > * The element will be converted to a `boolean`.
113 > *
114 > * @param scope allows to define the scope of the storage operation
115 > * to either the current workspace only, all workspaces or all profiles.
116 > */
117 > getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
118 > getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
119 >
120 > /**
121 > * Retrieve an element stored with the given key from storage. Use
122 > * the provided `defaultValue` if the element is `null` or `undefined`.
123 > * The element will be converted to a `number` using `parseInt` with a
124 > * base of `10`.
125 > *
126 > * @param scope allows to define the scope of the storage operation
127 > * to either the current workspace only, all workspaces or all profiles.
128 > */
129 > getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
130 > getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
131 >
132 > /**
133 > * Retrieve an element stored with the given key from storage. Use
134 > * the provided `defaultValue` if the element is `null` or `undefined`.
135 > * The element will be converted to a `object` using `JSON.parse`.
136 > *
137 > * @param scope allows to define the scope of the storage operation
138 > * to either the current workspace only, all workspaces or all profiles.
139 > */
140 > getObject<T extends object>(key: string, scope: StorageScope, fallbackValue: T): T;
141 > getObject<T extends object>(key: string, scope: StorageScope, fallbackValue?: T): T | undefined;
142 >
143 > /**
144 > * Store a value under the given key to storage. The value will be
145 > * converted to a `string`. Storing either `undefined` or `null` will
146 > * remove the entry under the key.
147 > *
148 > * @param scope allows to define the scope of the storage operation
149 > * to either the current workspace only, all workspaces or all profiles.
150 > *
151 > * @param target allows to define the target of the storage operation
152 > * to either the current machine or user.
153 > */
154 > store(key: string, value: StorageValue, scope: StorageScope, target: StorageTarget): void;
155 >
156 > /**
157 > * Allows to store multiple values in a bulk operation. Events will only
158 > * be emitted when all values have been stored.
159 > *
160 > * @param external a hint to indicate the source of the operation is external,
161 > * such as settings sync or profile changes.
162 > */
163 > storeAll(entries: Array<IStorageEntry>, external: boolean): void;
164 >
165 > /**
166 > * Delete an element stored under the provided key from storage.
167 > *
168 > * The scope argument allows to define the scope of the storage
169 > * operation to either the current workspace only, all workspaces
170 > * or all profiles.
171 > */
172 > remove(key: string, scope: StorageScope): void;
173 >
174 > /**
175 > * Returns all the keys used in the storage for the provided `scope`
176 > * and `target`.
177 > *
178 > * Note: this will NOT return all keys stored in the storage layer.
179 > * Some keys may not have an associated `StorageTarget` and thus
180 > * will be excluded from the results.
181 > *
182 > * @param scope allows to define the scope for the keys
183 > * to either the current workspace only, all workspaces or all profiles.
184 > *
185 > * @param target allows to define the target for the keys
186 > * to either the current machine or user.
187 > */
188 > keys(scope: StorageScope, target: StorageTarget): string[];
189 >
190 > /**
191 > * Log the contents of the storage to the console.
192 > */
193 > log(): void;
194 >
195 > /**
196 > * Returns true if the storage service handles the provided scope.
197 > */
198 > hasScope(scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean;
199 >
200 > /**
201 > * Switch storage to another workspace or profile. Optionally preserve the
202 > * current data to the new storage.
203 > */
204 > switch(to: IAnyWorkspaceIdentifier | IUserDataProfile, preserveData: boolean): Promise<void>;
205 >
206 > /**
207 > * Whether the storage for the given scope was created during this session or
208 > * existed before.
209 > */
210 > isNew(scope: StorageScope): boolean;
211 >
212 > /**
213 > * Attempts to reduce the DB size via optimization commands if supported.
214 > */
215 > optimize(scope: StorageScope): Promise<void>;
216 >
217 > /**
218 > * Allows to flush state, e.g. in cases where a shutdown is
219 > * imminent. This will send out the `onWillSaveState` to ask
220 > * everyone for latest state.
221 > *
222 > * @returns a `Promise` that can be awaited on when all updates
223 > * to the underlying storage have been flushed.
224 > */
225 > flush(reason?: WillSaveStateReason): Promise<void>;
226 > }
227 >
228 > export const enum StorageScope {
229 >
230 > /**
231 > * The stored data will be scoped to all workspaces across all profiles
232 > * and shared across VS Code and Sessions app.
233 > */
234 > APPLICATION_SHARED = -2,
235 >
236 > /**
237 > * The stored data will be scoped to all workspaces across all profiles.
238 > */
239 > APPLICATION = -1,
240 >
241 > /**
242 > * The stored data will be scoped to all workspaces of the same profile.
243 > */
244 > PROFILE = 0,
245 >
246 > /**
247 > * The stored data will be scoped to the current workspace.
248 > */
249 > WORKSPACE = 1
250 > }
251 >
252 > export const enum StorageTarget {
253 >
254 > /**
255 > * The stored data is user specific and applies across machines.
256 > */
257 > USER,
258 >
259 > /**
260 > * The stored data is machine specific.
261 > */
262 > MACHINE
263 > }
264 >
265 > export interface IStorageValueChangeEvent {
266 >
267 > /**
268 > * The scope for the storage entry that changed
269 > * or was removed.
270 > */
271 > readonly scope: StorageScope;
272 >
273 > /**
274 > * The `key` of the storage entry that was changed
275 > * or was removed.
276 > */
277 > readonly key: string;
278 >
279 > /**
280 > * The `target` can be `undefined` if a key is being
281 > * removed.
282 > */
283 > readonly target: StorageTarget | undefined;
284 >
285 > /**
286 > * A hint how the storage change event was triggered. If
287 > * `true`, the storage change was triggered by an external
288 > * source, such as:
289 > * - another process (for example another window)
290 > * - operations such as settings sync or profiles change
291 > */
292 > readonly external?: boolean;
293 > }
294 >
295 > export interface IStorageTargetChangeEvent {
296 >
297 > /**
298 > * The scope for the target that changed. Listeners
299 > * should use `keys(scope, target)` to get an updated
300 > * list of keys for the given `scope` and `target`.
301 > */
302 > readonly scope: StorageScope;
303 > }
304 >
305 > interface IKeyTargets {
306 > [key: string]: StorageTarget;
307 > }
308 >
309 > export interface IStorageServiceOptions {
310 > readonly flushInterval: number;
311 > }
312 >
313 > export function loadKeyTargets(storage: IStorage): IKeyTargets {
314 const keysRaw = storage.get(TARGET_KEY);
315 if (keysRaw) {
323 return Object.create(null);
324 }
325 > storage.ts
326 > export abstract class AbstractStorageService extends Disposable implements IStorageService {
327 >
328 > declare readonly _serviceBrand: undefined;
329 >
330 > private static DEFAULT_FLUSH_INTERVAL = 60 * 1000; // every minute
331 >
332 > private readonly _onDidChangeValue = this._register(new PauseableEmitter<IStorageValueChangeEvent>());
333 >
334 > private readonly _onDidChangeTarget = this._register(new PauseableEmitter<IStorageTargetChangeEvent>());
335 > readonly onDidChangeTarget = this._onDidChangeTarget.event;
336 >
337 > private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
338 > readonly onWillSaveState = this._onWillSaveState.event;
339 >
340 > private initializationPromise: Promise<void> | undefined;
341 >
342 > private readonly flushWhenIdleScheduler: RunOnceScheduler;
343 > private readonly runFlushWhenIdle = this._register(new MutableDisposable());
344 >
345 > constructor(options: IStorageServiceOptions = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
346 super();
347
348 this.flushWhenIdleScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), options.flushInterval));
349 }
350 > storage.ts
351 > onDidChangeValue(scope: StorageScope.WORKSPACE, key: string | undefined, disposable: DisposableStore): Event<IWorkspaceStorageValueChangeEvent>;
352 > onDidChangeValue(scope: StorageScope.PROFILE, key: string | undefined, disposable: DisposableStore): Event<IProfileStorageValueChangeEvent>;
353 > onDidChangeValue(scope: StorageScope.APPLICATION, key: string | undefined, disposable: DisposableStore): Event<IApplicationStorageValueChangeEvent>;
354 > onDidChangeValue(scope: StorageScope.APPLICATION_SHARED, key: string | undefined, disposable: DisposableStore): Event<IApplicationSharedStorageValueChangeEvent>;
355 > onDidChangeValue(scope: StorageScope, key: string | undefined, disposable: DisposableStore): Event<IStorageValueChangeEvent> {
356 return Event.filter(this._onDidChangeValue.event, e => e.scope === scope && (key === undefined || e.key === key), disposable);
357 }
358 > storage.ts
359 > private doFlushWhenIdle(): void {
360 this.runFlushWhenIdle.value = runWhenGlobalIdle(() => {
361 if (this.shouldFlushWhenIdle()) {
367 });
368 }
369 > storage.ts
370 > protected shouldFlushWhenIdle(): boolean {
371 return true;
372 }
373 > storage.ts
374 > protected stopFlushWhenIdle(): void {
375 dispose([this.runFlushWhenIdle, this.flushWhenIdleScheduler]);
376 }
377 > storage.ts
378 > initialize(): Promise<void> {
379 if (!this.initializationPromise) {
380 this.initializationPromise = (async () => {
402 return this.initializationPromise;
403 }
404 > storage.ts
405 > protected emitDidChangeValue(scope: StorageScope, event: IStorageChangeEvent): void {
406 const { key, external } = event;
407
434 }
435 }
436 > storage.ts
437 > protected emitWillSaveState(reason: WillSaveStateReason): void {
438 this._onWillSaveState.fire({ reason });
439 }
440 > storage.ts
441 > get(key: string, scope: StorageScope, fallbackValue: string): string;
442 > get(key: string, scope: StorageScope): string | undefined;
443 > get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
444 return this.getStorage(scope)?.get(key, fallbackValue);
445 }
446 > storage.ts
447 > getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
448 > getBoolean(key: string, scope: StorageScope): boolean | undefined;
449 > getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
450 return this.getStorage(scope)?.getBoolean(key, fallbackValue);
451 }
452 > storage.ts
453 > getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
454 > getNumber(key: string, scope: StorageScope): number | undefined;
455 > getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
456 return this.getStorage(scope)?.getNumber(key, fallbackValue);
457 }
458 > storage.ts
459 > getObject(key: string, scope: StorageScope, fallbackValue: object): object;
460 > getObject(key: string, scope: StorageScope): object | undefined;
461 > getObject(key: string, scope: StorageScope, fallbackValue?: object): object | undefined {
462 return this.getStorage(scope)?.getObject(key, fallbackValue);
463 }
464 > storage.ts
465 > storeAll(entries: Array<IStorageEntry>, external: boolean): void {
466 this.withPausedEmitters(() => {
467 for (const entry of entries) {
470 });
471 }
472 > storage.ts
473 > store(key: string, value: StorageValue, scope: StorageScope, target: StorageTarget, external = false): void {
474
475 // We remove the key for undefined/null values
489 });
490 }
491 > storage.ts
492 > remove(key: string, scope: StorageScope, external = false): void {
493
494 // Update our datastructures but send events only after
502 });
503 }
504 > storage.ts
505 > private withPausedEmitters(fn: Function): void {
506
507 // Pause emitters
518 }
519 }
520 > storage.ts
521 > keys(scope: StorageScope, target: StorageTarget): string[] {
522 const keys: string[] = [];
523
532 return keys;
533 }
534 > storage.ts
535 > private updateKeyTarget(key: string, scope: StorageScope, target: StorageTarget | undefined, external = false): void {
536
537 // Add
552 }
553 }
554 > storage.ts
555 > private _workspaceKeyTargets: IKeyTargets | undefined = undefined;
556 > private get workspaceKeyTargets(): IKeyTargets {
557 if (!this._workspaceKeyTargets) {
558 this._workspaceKeyTargets = this.loadKeyTargets(StorageScope.WORKSPACE);
561 return this._workspaceKeyTargets;
562 }
563 > storage.ts
564 > private _profileKeyTargets: IKeyTargets | undefined = undefined;
565 > private get profileKeyTargets(): IKeyTargets {
566 if (!this._profileKeyTargets) {
567 this._profileKeyTargets = this.loadKeyTargets(StorageScope.PROFILE);
570 return this._profileKeyTargets;
571 }
572 > storage.ts
573 > private _applicationKeyTargets: IKeyTargets | undefined = undefined;
574 > private get applicationKeyTargets(): IKeyTargets {
575 if (!this._applicationKeyTargets) {
576 this._applicationKeyTargets = this.loadKeyTargets(StorageScope.APPLICATION);
579 return this._applicationKeyTargets;
580 }
581 > storage.ts
582 > private _applicationSharedKeyTargets: IKeyTargets | undefined = undefined;
583 > private get applicationSharedKeyTargets(): IKeyTargets {
584 if (!this._applicationSharedKeyTargets) {
585 this._applicationSharedKeyTargets = this.loadKeyTargets(StorageScope.APPLICATION_SHARED);
588 return this._applicationSharedKeyTargets;
589 }
590 > storage.ts
591 > private getKeyTargets(scope: StorageScope): IKeyTargets {
592 switch (scope) {
593 case StorageScope.APPLICATION_SHARED:
601 }
602 }
603 > storage.ts
604 > private loadKeyTargets(scope: StorageScope): { [key: string]: StorageTarget } {
605 const storage = this.getStorage(scope);
606
607 return storage ? loadKeyTargets(storage) : Object.create(null);
608 }
609 > storage.ts
610 > isNew(scope: StorageScope): boolean {
611 return this.getBoolean(IS_NEW_KEY, scope) === true;
612 }
613 > storage.ts
614 > async flush(reason = WillSaveStateReason.NONE): Promise<void> {
615
616 // Signal event to collect changes
646 }
647 }
648 > storage.ts
649 > async log(): Promise<void> {
650 const applicationItems = this.getStorage(StorageScope.APPLICATION)?.items ?? new Map<string, string>();
651 const applicationSharedItems = this.getStorage(StorageScope.APPLICATION_SHARED)?.items ?? new Map<string, string>();
664 );
665 }
666 > storage.ts
667 > async optimize(scope: StorageScope): Promise<void> {
668
669 // Await pending data to be flushed to the DB
673 return this.getStorage(scope)?.optimize();
674 }
675 > storage.ts
676 > async switch(to: IAnyWorkspaceIdentifier | IUserDataProfile, preserveData: boolean): Promise<void> {
677
678 // Signal as event so that clients can store data before we switch
685 return this.switchToWorkspace(to, preserveData);
686 }
687 > storage.ts
688 > protected canSwitchProfile(from: IUserDataProfile, to: IUserDataProfile): boolean {
689 if (from.id === to.id) {
690 return false; // both profiles are same
697 return true;
698 }
699 > storage.ts
700 > protected switchData(oldStorage: Map<string, string>, newStorage: IStorage, scope: StorageScope): void {
701 this.withPausedEmitters(() => {
702 // Signal storage keys that have changed
718 });
719 }
720 > storage.ts
721 > // --- abstract
722 >
723 > abstract hasScope(scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean;
724 >
725 > protected abstract doInitialize(): Promise<void>;
726 >
727 > protected abstract getStorage(scope: StorageScope): IStorage | undefined;
728 >
729 > protected abstract getLogDetails(scope: StorageScope): string | undefined;
730 >
731 > protected abstract switchToProfile(toProfile: IUserDataProfile, preserveData: boolean): Promise<void>;
732 > protected abstract switchToWorkspace(toWorkspace: IAnyWorkspaceIdentifier | IUserDataProfile, preserveData: boolean): Promise<void>;
733 > }
734 >
735 > export function isProfileUsingDefaultStorage(profile: IUserDataProfile): boolean {
736 return profile.isDefault || !!profile.useDefaultFlags?.globalState;
737 }
738 > storage.ts
739 > export class InMemoryStorageService extends AbstractStorageService {
740 >
741 > private readonly applicationStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
742 > private readonly applicationSharedStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
743 > private readonly profileStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
744 > private readonly workspaceStorage = this._register(new Storage(new InMemoryStorageDatabase(), { hint: StorageHint.STORAGE_IN_MEMORY }));
745 >
746 > constructor() {
747 super();
748
752 this._register(this.applicationSharedStorage.onDidChangeStorage(e => this.emitDidChangeValue(StorageScope.APPLICATION_SHARED, e)));
753 }
754 > storage.ts
755 > protected getStorage(scope: StorageScope): IStorage {
756 switch (scope) {
757 case StorageScope.APPLICATION_SHARED:
765 }
766 }
767 > storage.ts
768 > protected getLogDetails(scope: StorageScope): string | undefined {
769 switch (scope) {
770 case StorageScope.APPLICATION_SHARED:
778 }
779 }
780 > storage.ts
781 > protected async doInitialize(): Promise<void> { }
782 >
783 > protected async switchToProfile(): Promise<void> {
784 // no-op when in-memory
785 }
786 > storage.ts
787 > protected async switchToWorkspace(): Promise<void> {
788 // no-op when in-memory
789 }
790 > storage.ts
791 > protected override shouldFlushWhenIdle(): boolean {
792 return false;
793 }
794 > storage.ts
795 > hasScope(scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean {
796 return false;
797 }
798 > } storage.ts
799 >
800 export async function logStorage(application: Map<string, string>, applicationShared: Map<string, string>, profile: Map<string, string>, workspace: Map<string, string>, applicationPath: string, applicationSharedPath: string, profilePath: string, workspacePath: string): Promise<void> {
801 const safeParse = (value: string) => {
src/vs/base/parts/storage/common/storage.ts 212 introduced LOC · 33 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- storage.ts
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 { ThrottledDelayer } from '../../../common/async.js';
7 > import { Event, PauseableEmitter } from '../../../common/event.js';
8 > import { Disposable, IDisposable } from '../../../common/lifecycle.js';
9 > import { parse, stringify } from '../../../common/marshalling.js';
10 > import { isObject, isUndefined, isUndefinedOrNull } from '../../../common/types.js';
11 >
12 > export enum StorageHint {
13 >
14 > // A hint to the storage that the storage
15 > // does not exist on disk yet. This allows
16 > // the storage library to improve startup
17 > // time by not checking the storage for data.
18 > STORAGE_DOES_NOT_EXIST,
19 >
20 > // A hint to the storage that the storage
21 > // is backed by an in-memory storage.
22 > STORAGE_IN_MEMORY
23 > }
24 >
25 > export interface IStorageOptions {
26 > readonly hint?: StorageHint;
27 > }
28 >
29 > export interface IUpdateRequest {
30 > readonly insert?: Map<string, string>;
31 > readonly delete?: Set<string>;
32 > }
33 >
34 > export interface IStorageItemsChangeEvent {
35 > readonly changed?: Map<string, string>;
36 > readonly deleted?: Set<string>;
37 > }
38 >
39 > export function isStorageItemsChangeEvent(thing: unknown): thing is IStorageItemsChangeEvent {
40 const candidate = thing as IStorageItemsChangeEvent | undefined;
41
42 return candidate?.changed instanceof Map || candidate?.deleted instanceof Set;
43 }
44 > storage.ts
45 > export interface IStorageDatabase {
46 >
47 > readonly onDidChangeItemsExternal: Event<IStorageItemsChangeEvent>;
48 >
49 > getItems(): Promise<Map<string, string>>;
50 > updateItems(request: IUpdateRequest): Promise<void>;
51 >
52 > optimize(): Promise<void>;
53 >
54 > close(recovery?: () => Map<string, string>): Promise<void>;
55 > }
56 >
57 > export interface IStorageChangeEvent {
58 >
59 > /**
60 > * The `key` of the storage entry that was changed
61 > * or was removed.
62 > */
63 > readonly key: string;
64 >
65 > /**
66 > * A hint how the storage change event was triggered. If
67 > * `true`, the storage change was triggered by an external
68 > * source, such as:
69 > * - another process (for example another window)
70 > * - operations such as settings sync or profiles change
71 > */
72 > readonly external?: boolean;
73 > }
74 >
75 > export type StorageValue = string | boolean | number | undefined | null | object;
76 >
77 > export interface IStorage extends IDisposable {
78 >
79 > readonly onDidChangeStorage: Event<IStorageChangeEvent>;
80 >
81 > readonly items: Map<string, string>;
82 > readonly size: number;
83 >
84 > init(): Promise<void>;
85 >
86 > get(key: string, fallbackValue: string): string;
87 > get(key: string, fallbackValue?: string): string | undefined;
88 >
89 > getBoolean(key: string, fallbackValue: boolean): boolean;
90 > getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
91 >
92 > getNumber(key: string, fallbackValue: number): number;
93 > getNumber(key: string, fallbackValue?: number): number | undefined;
94 >
95 > getObject<T extends object>(key: string, fallbackValue: T): T;
96 > getObject<T extends object>(key: string, fallbackValue?: T): T | undefined;
97 >
98 > set(key: string, value: StorageValue, external?: boolean): Promise<void>;
99 > delete(key: string, external?: boolean): Promise<void>;
100 >
101 > flush(delay?: number): Promise<void>;
102 > whenFlushed(): Promise<void>;
103 >
104 > optimize(): Promise<void>;
105 >
106 > close(): Promise<void>;
107 > }
108 >
109 > export enum StorageState {
110 > None,
111 > Initialized,
112 > Closed
113 > }
114 >
115 > export class Storage extends Disposable implements IStorage {
116 >
117 > private static readonly DEFAULT_FLUSH_DELAY = 100;
118 >
119 > private readonly _onDidChangeStorage = this._register(new PauseableEmitter<IStorageChangeEvent>());
120 > readonly onDidChangeStorage = this._onDidChangeStorage.event;
121 >
122 > private state = StorageState.None;
123 >
124 > private cache = new Map<string, string>();
125 >
126 > private readonly flushDelayer = this._register(new ThrottledDelayer<void>(Storage.DEFAULT_FLUSH_DELAY));
127 >
128 > private pendingDeletes = new Set<string>();
129 > private pendingInserts = new Map<string, string>();
130 >
131 > private pendingClose: Promise<void> | undefined = undefined;
132 >
133 > private readonly whenFlushedCallbacks: Function[] = [];
134 >
135 > constructor(
136 protected readonly database: IStorageDatabase,
137 private readonly options: IStorageOptions = Object.create(null)
141 this.registerListeners();
142 }
143 > storage.ts
144 > private registerListeners(): void {
145 this._register(this.database.onDidChangeItemsExternal(e => this.onDidChangeItemsExternal(e)));
146 }
147 > storage.ts
148 > private onDidChangeItemsExternal(e: IStorageItemsChangeEvent): void {
149 this._onDidChangeStorage.pause();
150
161 }
162 }
163 > storage.ts
164 > private acceptExternal(key: string, value: string | undefined): void {
165 if (this.state === StorageState.Closed) {
166 return; // Return early if we are already closed
188 }
189 }
190 > storage.ts
191 > get items(): Map<string, string> {
192 return this.cache;
193 }
194 > storage.ts
195 > get size(): number {
196 return this.cache.size;
197 }
198 > storage.ts
199 > async init(): Promise<void> {
200 if (this.state !== StorageState.None) {
201 return; // either closed or already initialized
213 this.cache = await this.database.getItems();
214 }
215 > storage.ts
216 > get(key: string, fallbackValue: string): string;
217 > get(key: string, fallbackValue?: string): string | undefined;
218 > get(key: string, fallbackValue?: string): string | undefined {
219 const value = this.cache.get(key);
220
225 return value;
226 }
227 > storage.ts
228 > getBoolean(key: string, fallbackValue: boolean): boolean;
229 > getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
230 > getBoolean(key: string, fallbackValue?: boolean): boolean | undefined {
231 const value = this.get(key);
232
237 return value === 'true';
238 }
239 > storage.ts
240 > getNumber(key: string, fallbackValue: number): number;
241 > getNumber(key: string, fallbackValue?: number): number | undefined;
242 > getNumber(key: string, fallbackValue?: number): number | undefined {
243 const value = this.get(key);
244
249 return parseInt(value, 10);
250 }
251 > storage.ts
252 > getObject(key: string, fallbackValue: object): object;
253 > getObject(key: string, fallbackValue?: object | undefined): object | undefined;
254 > getObject(key: string, fallbackValue?: object): object | undefined {
255 const value = this.get(key);
256
261 return parse(value);
262 }
263 > storage.ts
264 > async set(key: string, value: string | boolean | number | null | undefined | object, external = false): Promise<void> {
265 if (this.state === StorageState.Closed) {
266 return; // Return early if we are already closed
292 return this.doFlush();
293 }
294 > storage.ts
295 > async delete(key: string, external = false): Promise<void> {
296 if (this.state === StorageState.Closed) {
297 return; // Return early if we are already closed
316 return this.doFlush();
317 }
318 > storage.ts
319 > async optimize(): Promise<void> {
320 if (this.state === StorageState.Closed) {
321 return; // Return early if we are already closed
328 return this.database.optimize();
329 }
330 > storage.ts
331 > async close(): Promise<void> {
332 if (!this.pendingClose) {
333 this.pendingClose = this.doClose();
336 return this.pendingClose;
337 }
338 > storage.ts
339 > private async doClose(): Promise<void> {
340
341 // Update state
356 await this.database.close(() => this.cache);
357 }
358 > storage.ts
359 > private get hasPending() {
360 return this.pendingInserts.size > 0 || this.pendingDeletes.size > 0;
361 }
362 > storage.ts
363 > private async flushPending(): Promise<void> {
364 if (!this.hasPending) {
365 return; // return early if nothing to do
383 });
384 }
385 > storage.ts
386 > async flush(delay?: number): Promise<void> {
387 if (
388 this.state === StorageState.Closed || // Return early if we are already closed
394 return this.doFlush(delay);
395 }
396 > storage.ts
397 > private async doFlush(delay?: number): Promise<void> {
398 if (this.options.hint === StorageHint.STORAGE_IN_MEMORY) {
399 return this.flushPending(); // return early if in-memory
402 return this.flushDelayer.trigger(() => this.flushPending(), delay);
403 }
404 > storage.ts
405 > async whenFlushed(): Promise<void> {
406 if (!this.hasPending) {
407 return; // return early if nothing to do
410 return new Promise(resolve => this.whenFlushedCallbacks.push(resolve));
411 }
412 > storage.ts
413 > isInMemory(): boolean {
414 return this.options.hint === StorageHint.STORAGE_IN_MEMORY;
415 }
416 > } storage.ts
417 >
418 > export class InMemoryStorageDatabase implements IStorageDatabase {
419
420 readonly onDidChangeItemsExternal = Event.None;
421
422 private readonly items = new Map<string, string>();
423 > storage.ts
424 > async getItems(): Promise<Map<string, string>> {
425 return this.items;
426 }
427 > storage.ts
428 > async updateItems(request: IUpdateRequest): Promise<void> {
429 request.insert?.forEach((value, key) => this.items.set(key, value));
430
431 request.delete?.forEach(key => this.items.delete(key));
432 }
433 > storage.ts
434 > async optimize(): Promise<void> { }
435 > async close(): Promise<void> { }
436 > }
437 >
438 >
439 > export const MIGRATED_KEY = '__$__migratedStorageMarker';
440 >
441 > export class MigratingStorage extends Storage {
442
443 private migratedKeys: Set<string> = new Set();
444 private fallbackStorage: IStorage | undefined = undefined;
445 private isFallbackStorageReadonly: boolean = false;
446 > storage.ts
447 > override async init(): Promise<void> {
448 await super.init();
449
451 this.migratedKeys = this.loadMigratedKeys();
452 }
453 > storage.ts
454 > public setFallbackStorage(storage: IStorage, isReadonly: boolean): void {
455 this.fallbackStorage = storage;
456 this.isFallbackStorageReadonly = isReadonly;
457 }
458 > storage.ts
459 > private static readonly INTERNAL_KEY_PREFIX = '__$__';
460 >
461 > override get(key: string, fallbackValue: string): string;
462 > override get(key: string, fallbackValue?: string): string | undefined;
463 > override get(key: string, fallbackValue?: string): string | undefined {
464 if (!key.startsWith(MigratingStorage.INTERNAL_KEY_PREFIX) && !this.migratedKeys.has(key) && isUndefined(super.get(key))) {
465 // Check fallback storage and auto-migrate on hit.
480 return super.get(key, fallbackValue);
481 }
482 > storage.ts
483 > private loadMigratedKeys(): Set<string> {
484 const raw = super.get(MIGRATED_KEY);
485 if (raw) {
492 return new Set();
493 }
494 > storage.ts
495 > private persistMigratedKeys(): void {
496 this.set(MIGRATED_KEY, JSON.stringify([...this.migratedKeys]));
497 }
498 > } storage.ts
499