mirror of
https://github.com/coder/code-server.git
synced 2026-05-10 14:27:26 +02:00
chore(vscode): update to 1.54.2
This commit is contained in:
@@ -3,21 +3,22 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { StorageScope, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IFileService, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { runWhenIdle, RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { assertIsDefined, assertAllDefined } from 'vs/base/common/types';
|
||||
|
||||
export class BrowserStorageService extends AbstractStorageService {
|
||||
|
||||
private static BROWSER_DEFAULT_FLUSH_INTERVAL = 5 * 1000; // every 5s because async operations are not permitted on shutdown
|
||||
|
||||
private globalStorage: IStorage | undefined;
|
||||
private workspaceStorage: IStorage | undefined;
|
||||
|
||||
@@ -27,38 +28,26 @@ export class BrowserStorageService extends AbstractStorageService {
|
||||
private globalStorageFile: URI | undefined;
|
||||
private workspaceStorageFile: URI | undefined;
|
||||
|
||||
private initializePromise: Promise<void> | undefined;
|
||||
|
||||
private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 5000 /* every 5s */));
|
||||
private runWhenIdleDisposable: IDisposable | undefined = undefined;
|
||||
|
||||
get hasPendingUpdate(): boolean {
|
||||
return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly payload: IWorkspaceInitializationPayload,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
super({ flushInterval: BrowserStorageService.BROWSER_DEFAULT_FLUSH_INTERVAL });
|
||||
}
|
||||
|
||||
initialize(payload: IWorkspaceInitializationPayload): Promise<void> {
|
||||
if (!this.initializePromise) {
|
||||
this.initializePromise = this.doInitialize(payload);
|
||||
}
|
||||
|
||||
return this.initializePromise;
|
||||
}
|
||||
|
||||
private async doInitialize(payload: IWorkspaceInitializationPayload): Promise<void> {
|
||||
protected async doInitialize(): Promise<void> {
|
||||
|
||||
// Ensure state folder exists
|
||||
const stateRoot = joinPath(this.environmentService.userRoamingDataHome, 'state');
|
||||
await this.fileService.createFolder(stateRoot);
|
||||
|
||||
// Workspace Storage
|
||||
this.workspaceStorageFile = joinPath(stateRoot, `${payload.id}.json`);
|
||||
this.workspaceStorageFile = joinPath(stateRoot, `${this.payload.id}.json`);
|
||||
|
||||
this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService));
|
||||
this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase));
|
||||
@@ -71,7 +60,7 @@ export class BrowserStorageService extends AbstractStorageService {
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
|
||||
// Init both
|
||||
await Promise.all([
|
||||
await Promises.settled([
|
||||
this.workspaceStorage.init(),
|
||||
this.globalStorage.init()
|
||||
]);
|
||||
@@ -91,91 +80,36 @@ export class BrowserStorageService extends AbstractStorageService {
|
||||
} else if (firstWorkspaceOpen) {
|
||||
this.workspaceStorage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
|
||||
// In the browser we do not have support for long running unload sequences. As such,
|
||||
// we cannot ask for saving state in that moment, because that would result in a
|
||||
// long running operation.
|
||||
// Instead, periodically ask customers to save save. The library will be clever enough
|
||||
// to only save state that has actually changed.
|
||||
this.periodicFlushScheduler.schedule();
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope): string | undefined;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
|
||||
return this.getStorage(scope).get(key, fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, scope: StorageScope): boolean | undefined;
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
|
||||
return this.getStorage(scope).getBoolean(key, fallbackValue);
|
||||
}
|
||||
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getNumber(key: string, scope: StorageScope): number | undefined;
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
|
||||
return this.getStorage(scope).getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): Promise<void> {
|
||||
return this.getStorage(scope).set(key, value);
|
||||
=======
|
||||
protected getStorage(scope: StorageScope): IStorage | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
|
||||
>>>>>>> e8cd17a97d8c58fffcbac05394b3ee2b3c72d384
|
||||
}
|
||||
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
this.getStorage(scope).delete(key);
|
||||
}
|
||||
|
||||
private getStorage(scope: StorageScope): IStorage {
|
||||
return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage);
|
||||
}
|
||||
|
||||
async logStorage(): Promise<void> {
|
||||
const [globalStorage, workspaceStorage, globalStorageFile, workspaceStorageFile] = assertAllDefined(this.globalStorage, this.workspaceStorage, this.globalStorageFile, this.workspaceStorageFile);
|
||||
|
||||
const result = await Promise.all([
|
||||
globalStorage.items,
|
||||
workspaceStorage.items
|
||||
]);
|
||||
|
||||
return logStorage(result[0], result[1], globalStorageFile.toString(), workspaceStorageFile.toString());
|
||||
protected getLogDetails(scope: StorageScope): string | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorageFile?.toString() : this.workspaceStorageFile?.toString();
|
||||
}
|
||||
|
||||
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
throw new Error('Migrating storage is currently unsupported in Web');
|
||||
}
|
||||
|
||||
protected async doFlush(): Promise<void> {
|
||||
await Promise.all([
|
||||
this.getStorage(StorageScope.GLOBAL).whenFlushed(),
|
||||
this.getStorage(StorageScope.WORKSPACE).whenFlushed()
|
||||
]);
|
||||
}
|
||||
|
||||
private doFlushWhenIdle(): void {
|
||||
|
||||
// Dispose any previous idle runner
|
||||
dispose(this.runWhenIdleDisposable);
|
||||
|
||||
// Run when idle
|
||||
this.runWhenIdleDisposable = runWhenIdle(() => {
|
||||
|
||||
// this event will potentially cause new state to be stored
|
||||
// since new state will only be created while the document
|
||||
// has focus, one optimization is to not run this when the
|
||||
// document has no focus, assuming that state has not changed
|
||||
//
|
||||
// another optimization is to not collect more state if we
|
||||
// have a pending update already running which indicates
|
||||
// that the connection is either slow or disconnected and
|
||||
// thus unhealthy.
|
||||
if (document.hasFocus() && !this.hasPendingUpdate) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
// repeat
|
||||
this.periodicFlushScheduler.schedule();
|
||||
});
|
||||
protected shouldFlushWhenIdle(): boolean {
|
||||
// this flush() will potentially cause new state to be stored
|
||||
// since new state will only be created while the document
|
||||
// has focus, one optimization is to not run this when the
|
||||
// document has no focus, assuming that state has not changed
|
||||
//
|
||||
// another optimization is to not collect more state if we
|
||||
// have a pending update already running which indicates
|
||||
// that the connection is either slow or disconnected and
|
||||
// thus unhealthy.
|
||||
return document.hasFocus() && !this.hasPendingUpdate;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
@@ -189,13 +123,6 @@ export class BrowserStorageService extends AbstractStorageService {
|
||||
// get triggered in this phase.
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.runWhenIdleDisposable);
|
||||
this.runWhenIdleDisposable = undefined;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class FileStorageDatabase extends Disposable implements IStorageDatabase {
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter, PauseableEmitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, dispose, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { InMemoryStorageDatabase, IStorage, Storage } from 'vs/base/parts/storage/common/storage';
|
||||
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
|
||||
|
||||
export const IS_NEW_KEY = '__$__isNewStorageMarker';
|
||||
const TARGET_KEY = '__$__targetStorageMarker';
|
||||
@@ -218,10 +220,16 @@ interface IKeyTargets {
|
||||
[key: string]: StorageTarget
|
||||
}
|
||||
|
||||
export interface IStorageServiceOptions {
|
||||
flushInterval: number;
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageService extends Disposable implements IStorageService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static DEFAULT_FLUSH_INTERVAL = 60 * 1000; // every minute
|
||||
|
||||
private readonly _onDidChangeValue = this._register(new PauseableEmitter<IStorageValueChangeEvent>());
|
||||
readonly onDidChangeValue = this._onDidChangeValue.event;
|
||||
|
||||
@@ -231,6 +239,56 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
private readonly _onWillSaveState = this._register(new Emitter<IWillSaveStateEvent>());
|
||||
readonly onWillSaveState = this._onWillSaveState.event;
|
||||
|
||||
private initializationPromise: Promise<void> | undefined;
|
||||
|
||||
private readonly flushWhenIdleScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), this.options.flushInterval));
|
||||
private readonly runFlushWhenIdle = this._register(new MutableDisposable());
|
||||
|
||||
constructor(private options: IStorageServiceOptions = { flushInterval: AbstractStorageService.DEFAULT_FLUSH_INTERVAL }) {
|
||||
super();
|
||||
}
|
||||
|
||||
private doFlushWhenIdle(): void {
|
||||
this.runFlushWhenIdle.value = runWhenIdle(() => {
|
||||
if (this.shouldFlushWhenIdle()) {
|
||||
this.flush();
|
||||
}
|
||||
|
||||
// repeat
|
||||
this.flushWhenIdleScheduler.schedule();
|
||||
});
|
||||
}
|
||||
|
||||
protected shouldFlushWhenIdle(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected stopFlushWhenIdle(): void {
|
||||
dispose([this.runFlushWhenIdle, this.flushWhenIdleScheduler]);
|
||||
}
|
||||
|
||||
initialize(): Promise<void> {
|
||||
if (!this.initializationPromise) {
|
||||
this.initializationPromise = (async () => {
|
||||
|
||||
// Ask subclasses to initialize storage
|
||||
await this.doInitialize();
|
||||
|
||||
// On some OS we do not get enough time to persist state on shutdown (e.g. when
|
||||
// Windows restarts after applying updates). In other cases, VSCode might crash,
|
||||
// so we periodically save state to reduce the chance of loosing any state.
|
||||
// In the browser we do not have support for long running unload sequences. As such,
|
||||
// we cannot ask for saving state in that moment, because that would result in a
|
||||
// long running operation.
|
||||
// Instead, periodically ask customers to save save. The library will be clever enough
|
||||
// to only save state that has actually changed.
|
||||
this.flushWhenIdleScheduler.schedule();
|
||||
})();
|
||||
}
|
||||
|
||||
return this.initializationPromise;
|
||||
}
|
||||
|
||||
protected emitDidChangeValue(scope: StorageScope, key: string): void {
|
||||
|
||||
// Specially handle `TARGET_KEY`
|
||||
@@ -257,6 +315,24 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
this._onWillSaveState.fire({ reason });
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope): string | undefined;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
|
||||
return this.getStorage(scope)?.get(key, fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, scope: StorageScope): boolean | undefined;
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
|
||||
return this.getStorage(scope)?.getBoolean(key, fallbackValue);
|
||||
}
|
||||
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getNumber(key: string, scope: StorageScope): number | undefined;
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
|
||||
return this.getStorage(scope)?.getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null, scope: StorageScope, target: StorageTarget): void {
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
@@ -272,7 +348,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
this.updateKeyTarget(key, scope, target);
|
||||
|
||||
// Store actual value
|
||||
this.doStore(key, value, scope);
|
||||
this.getStorage(scope)?.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -285,7 +361,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
this.updateKeyTarget(key, scope, undefined);
|
||||
|
||||
// Remove actual key
|
||||
this.doRemove(key, scope);
|
||||
this.getStorage(scope)?.delete(key);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -326,7 +402,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
if (typeof target === 'number') {
|
||||
if (keyTargets[key] !== target) {
|
||||
keyTargets[key] = target;
|
||||
this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope);
|
||||
this.getStorage(scope)?.set(TARGET_KEY, JSON.stringify(keyTargets));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +410,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
else {
|
||||
if (typeof keyTargets[key] === 'number') {
|
||||
delete keyTargets[key];
|
||||
this.doStore(TARGET_KEY, JSON.stringify(keyTargets), scope);
|
||||
this.getStorage(scope)?.set(TARGET_KEY, JSON.stringify(keyTargets));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,118 +454,70 @@ export abstract class AbstractStorageService extends Disposable implements IStor
|
||||
return this.getBoolean(IS_NEW_KEY, scope) === true;
|
||||
}
|
||||
|
||||
flush(): Promise<void> {
|
||||
async flush(): Promise<void> {
|
||||
|
||||
// Signal event to collect changes
|
||||
this._onWillSaveState.fire({ reason: WillSaveStateReason.NONE });
|
||||
|
||||
// Await flush
|
||||
return this.doFlush();
|
||||
await Promises.settled([
|
||||
this.getStorage(StorageScope.GLOBAL)?.whenFlushed() ?? Promise.resolve(),
|
||||
this.getStorage(StorageScope.WORKSPACE)?.whenFlushed() ?? Promise.resolve()
|
||||
]);
|
||||
}
|
||||
|
||||
async logStorage(): Promise<void> {
|
||||
const globalItems = this.getStorage(StorageScope.GLOBAL)?.items ?? new Map<string, string>();
|
||||
const workspaceItems = this.getStorage(StorageScope.WORKSPACE)?.items ?? new Map<string, string>();
|
||||
|
||||
return logStorage(
|
||||
globalItems,
|
||||
workspaceItems,
|
||||
this.getLogDetails(StorageScope.GLOBAL) ?? '',
|
||||
this.getLogDetails(StorageScope.WORKSPACE) ?? ''
|
||||
);
|
||||
}
|
||||
|
||||
// --- abstract
|
||||
|
||||
abstract get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
abstract get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined;
|
||||
|
||||
abstract getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
abstract getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
abstract getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
abstract getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined;
|
||||
|
||||
<<<<<<< HEAD
|
||||
protected abstract doStore(key: string, value: string | boolean | number, scope: StorageScope): Promise<void> | void;
|
||||
=======
|
||||
protected abstract doInitialize(): Promise<void>;
|
||||
>>>>>>> e8cd17a97d8c58fffcbac05394b3ee2b3c72d384
|
||||
|
||||
protected abstract doRemove(key: string, scope: StorageScope): void;
|
||||
protected abstract getStorage(scope: StorageScope): IStorage | undefined;
|
||||
|
||||
protected abstract doFlush(): Promise<void>;
|
||||
protected abstract getLogDetails(scope: StorageScope): string | undefined;
|
||||
|
||||
abstract migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void>;
|
||||
|
||||
abstract logStorage(): void;
|
||||
}
|
||||
|
||||
export class InMemoryStorageService extends AbstractStorageService {
|
||||
|
||||
private readonly globalCache = new Map<string, string>();
|
||||
private readonly workspaceCache = new Map<string, string>();
|
||||
private globalStorage = new Storage(new InMemoryStorageDatabase());
|
||||
private workspaceStorage = new Storage(new InMemoryStorageDatabase());
|
||||
|
||||
private getCache(scope: StorageScope): Map<string, string> {
|
||||
return scope === StorageScope.GLOBAL ? this.globalCache : this.workspaceCache;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._register(this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)));
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
|
||||
const value = this.getCache(scope).get(key);
|
||||
|
||||
if (isUndefinedOrNull(value)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
protected getStorage(scope: StorageScope): IStorage {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
|
||||
}
|
||||
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined {
|
||||
const value = this.getCache(scope).get(key);
|
||||
|
||||
if (isUndefinedOrNull(value)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return value === 'true';
|
||||
protected getLogDetails(scope: StorageScope): string | undefined {
|
||||
return scope === StorageScope.GLOBAL ? 'inMemory (global)' : 'inMemory (workspace)';
|
||||
}
|
||||
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue: number): number;
|
||||
getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined {
|
||||
const value = this.getCache(scope).get(key);
|
||||
|
||||
if (isUndefinedOrNull(value)) {
|
||||
return fallbackValue;
|
||||
}
|
||||
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
protected doStore(key: string, value: string | boolean | number, scope: StorageScope): void {
|
||||
|
||||
// Otherwise, convert to String and store
|
||||
const valueStr = String(value);
|
||||
|
||||
// Return early if value already set
|
||||
const currentValue = this.getCache(scope).get(key);
|
||||
if (currentValue === valueStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update in cache
|
||||
this.getCache(scope).set(key, valueStr);
|
||||
|
||||
// Events
|
||||
this.emitDidChangeValue(scope, key);
|
||||
}
|
||||
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
const wasDeleted = this.getCache(scope).delete(key);
|
||||
if (!wasDeleted) {
|
||||
return; // Return early if value already deleted
|
||||
}
|
||||
|
||||
// Events
|
||||
this.emitDidChangeValue(scope, key);
|
||||
}
|
||||
|
||||
logStorage(): void {
|
||||
logStorage(this.globalCache, this.workspaceCache, 'inMemory', 'inMemory');
|
||||
}
|
||||
protected async doInitialize(): Promise<void> { }
|
||||
|
||||
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
|
||||
async doFlush(): Promise<void> { }
|
||||
|
||||
async close(): Promise<void> { }
|
||||
}
|
||||
|
||||
export async function logStorage(global: Map<string, string>, workspace: Map<string, string>, globalPath: string, workspacePath: string): Promise<void> {
|
||||
|
||||
136
lib/vscode/src/vs/platform/storage/common/storageIpc.ts
Normal file
136
lib/vscode/src/vs/platform/storage/common/storageIpc.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage';
|
||||
import { IEmptyWorkspaceIdentifier, ISerializedSingleFolderWorkspaceIdentifier, ISerializedWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export type Key = string;
|
||||
export type Value = string;
|
||||
export type Item = [Key, Value];
|
||||
|
||||
export interface IBaseSerializableStorageRequest {
|
||||
readonly workspace: ISerializedWorkspaceIdentifier | ISerializedSingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined
|
||||
}
|
||||
|
||||
export interface ISerializableUpdateRequest extends IBaseSerializableStorageRequest {
|
||||
insert?: Item[];
|
||||
delete?: Key[];
|
||||
}
|
||||
|
||||
export interface ISerializableItemsChangeEvent {
|
||||
readonly changed?: Item[];
|
||||
readonly deleted?: Key[];
|
||||
}
|
||||
|
||||
abstract class BaseStorageDatabaseClient extends Disposable implements IStorageDatabase {
|
||||
|
||||
abstract readonly onDidChangeItemsExternal: Event<IStorageItemsChangeEvent>;
|
||||
|
||||
constructor(protected channel: IChannel, protected workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getItems(): Promise<Map<string, string>> {
|
||||
const serializableRequest: IBaseSerializableStorageRequest = { workspace: this.workspace };
|
||||
const items: Item[] = await this.channel.call('getItems', serializableRequest);
|
||||
|
||||
return new Map(items);
|
||||
}
|
||||
|
||||
updateItems(request: IUpdateRequest): Promise<void> {
|
||||
const serializableRequest: ISerializableUpdateRequest = { workspace: this.workspace };
|
||||
|
||||
if (request.insert) {
|
||||
serializableRequest.insert = Array.from(request.insert.entries());
|
||||
}
|
||||
|
||||
if (request.delete) {
|
||||
serializableRequest.delete = Array.from(request.delete.values());
|
||||
}
|
||||
|
||||
return this.channel.call('updateItems', serializableRequest);
|
||||
}
|
||||
|
||||
abstract close(): Promise<void>;
|
||||
}
|
||||
|
||||
class GlobalStorageDatabaseClient extends BaseStorageDatabaseClient implements IStorageDatabase {
|
||||
|
||||
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
|
||||
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
|
||||
|
||||
constructor(channel: IChannel) {
|
||||
super(channel, undefined);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.channel.listen<ISerializableItemsChangeEvent>('onDidChangeGlobalStorage')((e: ISerializableItemsChangeEvent) => this.onDidChangeGlobalStorage(e)));
|
||||
}
|
||||
|
||||
private onDidChangeGlobalStorage(e: ISerializableItemsChangeEvent): void {
|
||||
if (Array.isArray(e.changed) || Array.isArray(e.deleted)) {
|
||||
this._onDidChangeItemsExternal.fire({
|
||||
changed: e.changed ? new Map(e.changed) : undefined,
|
||||
deleted: e.deleted ? new Set<string>(e.deleted) : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// The global storage database is shared across all instances so
|
||||
// we do not await it. However we dispose the listener for external
|
||||
// changes because we no longer interested int it.
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceStorageDatabaseClient extends BaseStorageDatabaseClient implements IStorageDatabase {
|
||||
|
||||
readonly onDidChangeItemsExternal = Event.None; // unsupported for workspace storage because we only ever write from one window
|
||||
|
||||
constructor(channel: IChannel, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier) {
|
||||
super(channel, workspace);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
const serializableRequest: ISerializableUpdateRequest = { workspace: this.workspace };
|
||||
|
||||
return this.channel.call('close', serializableRequest);
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageDatabaseChannelClient extends Disposable {
|
||||
|
||||
private _globalStorage: GlobalStorageDatabaseClient | undefined = undefined;
|
||||
get globalStorage() {
|
||||
if (!this._globalStorage) {
|
||||
this._globalStorage = new GlobalStorageDatabaseClient(this.channel);
|
||||
}
|
||||
|
||||
return this._globalStorage;
|
||||
}
|
||||
|
||||
private _workspaceStorage: WorkspaceStorageDatabaseClient | undefined = undefined;
|
||||
get workspaceStorage() {
|
||||
if (!this._workspaceStorage && this.workspace) {
|
||||
this._workspaceStorage = new WorkspaceStorageDatabaseClient(this.channel, this.workspace);
|
||||
}
|
||||
|
||||
return this._workspaceStorage;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private channel: IChannel,
|
||||
private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
136
lib/vscode/src/vs/platform/storage/electron-main/storageIpc.ts
Normal file
136
lib/vscode/src/vs/platform/storage/electron-main/storageIpc.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ISerializableItemsChangeEvent, ISerializableUpdateRequest, IBaseSerializableStorageRequest, Key, Value } from 'vs/platform/storage/common/storageIpc';
|
||||
import { IStorageChangeEvent, IStorageMain } from 'vs/platform/storage/electron-main/storageMain';
|
||||
import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
|
||||
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export class StorageDatabaseChannel extends Disposable implements IServerChannel {
|
||||
|
||||
private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100;
|
||||
|
||||
private readonly _onDidChangeGlobalStorage = this._register(new Emitter<ISerializableItemsChangeEvent>());
|
||||
private readonly onDidChangeGlobalStorage = this._onDidChangeGlobalStorage.event;
|
||||
|
||||
constructor(
|
||||
private logService: ILogService,
|
||||
private storageMainService: IStorageMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerGlobalStorageListeners();
|
||||
}
|
||||
|
||||
//#region Global Storage Change Events
|
||||
|
||||
private registerGlobalStorageListeners(): void {
|
||||
|
||||
// Listen for changes in global storage to send to listeners
|
||||
// that are listening. Use a debouncer to reduce IPC traffic.
|
||||
this._register(Event.debounce(this.storageMainService.globalStorage.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => {
|
||||
if (!prev) {
|
||||
prev = [cur];
|
||||
} else {
|
||||
prev.push(cur);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, StorageDatabaseChannel.STORAGE_CHANGE_DEBOUNCE_TIME)(events => {
|
||||
if (events.length) {
|
||||
this._onDidChangeGlobalStorage.fire(this.serializeGlobalStorageEvents(events));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private serializeGlobalStorageEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
|
||||
const changed = new Map<Key, Value>();
|
||||
const deleted = new Set<Key>();
|
||||
events.forEach(event => {
|
||||
const existing = this.storageMainService.globalStorage.get(event.key);
|
||||
if (typeof existing === 'string') {
|
||||
changed.set(event.key, existing);
|
||||
} else {
|
||||
deleted.add(event.key);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
changed: Array.from(changed.entries()),
|
||||
deleted: Array.from(deleted.values())
|
||||
};
|
||||
}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeGlobalStorage': return this.onDidChangeGlobalStorage;
|
||||
}
|
||||
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
async call(_: unknown, command: string, arg: IBaseSerializableStorageRequest): Promise<any> {
|
||||
const workspace = reviveIdentifier(arg.workspace);
|
||||
|
||||
// Get storage to be ready
|
||||
const storage = await this.withStorageInitialized(workspace);
|
||||
|
||||
// handle call
|
||||
switch (command) {
|
||||
case 'getItems': {
|
||||
return Array.from(storage.items.entries());
|
||||
}
|
||||
|
||||
case 'updateItems': {
|
||||
const items: ISerializableUpdateRequest = arg;
|
||||
|
||||
if (items.insert) {
|
||||
for (const [key, value] of items.insert) {
|
||||
storage.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (items.delete) {
|
||||
items.delete.forEach(key => storage.delete(key));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'close': {
|
||||
|
||||
// We only allow to close workspace scoped storage because
|
||||
// global storage is shared across all windows and closes
|
||||
// only on shutdown.
|
||||
if (workspace) {
|
||||
return storage.close();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Call not found: ${command}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async withStorageInitialized(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): Promise<IStorageMain> {
|
||||
const storage = workspace ? this.storageMainService.workspaceStorage(workspace) : this.storageMainService.globalStorage;
|
||||
|
||||
try {
|
||||
await storage.init();
|
||||
} catch (error) {
|
||||
this.logService.error(`StorageIPC#init: Unable to init ${workspace ? 'workspace' : 'global'} storage due to ${error}`);
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
}
|
||||
307
lib/vscode/src/vs/platform/storage/electron-main/storageMain.ts
Normal file
307
lib/vscode/src/vs/platform/storage/electron-main/storageMain.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { exists, writeFile } from 'vs/base/node/pfs';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, InMemoryStorageDatabase, StorageHint, IStorage } from 'vs/base/parts/storage/common/storage';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export interface IStorageMainOptions {
|
||||
|
||||
/**
|
||||
* If enabled, storage will not persist to disk
|
||||
* but into memory.
|
||||
*/
|
||||
useInMemoryStorage?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to global and workspace storage from the
|
||||
* electron-main side that is the owner of all storage connections.
|
||||
*/
|
||||
export interface IStorageMain extends IDisposable {
|
||||
|
||||
/**
|
||||
* Emitted whenever data is updated or deleted.
|
||||
*/
|
||||
readonly onDidChangeStorage: Event<IStorageChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the storage is closed.
|
||||
*/
|
||||
readonly onDidCloseStorage: Event<void>;
|
||||
|
||||
/**
|
||||
* Access to all cached items of this storage service.
|
||||
*/
|
||||
readonly items: Map<string, string>;
|
||||
|
||||
/**
|
||||
* Required call to ensure the service can be used.
|
||||
*/
|
||||
init(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
*/
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to storage. The value will
|
||||
* be converted to a string.
|
||||
*/
|
||||
set(key: string, value: string | boolean | number | undefined | null): void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
*/
|
||||
delete(key: string): void;
|
||||
|
||||
/**
|
||||
* Close the storage connection.
|
||||
*/
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IStorageChangeEvent {
|
||||
key: string;
|
||||
}
|
||||
|
||||
abstract class BaseStorageMain extends Disposable implements IStorageMain {
|
||||
|
||||
protected readonly _onDidChangeStorage = this._register(new Emitter<IStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
private readonly _onDidCloseStorage = this._register(new Emitter<void>());
|
||||
readonly onDidCloseStorage = this._onDidCloseStorage.event;
|
||||
|
||||
private storage: IStorage = new Storage(new InMemoryStorageDatabase()); // storage is in-memory until initialized
|
||||
|
||||
private initializePromise: Promise<void> | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
protected readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
init(): Promise<void> {
|
||||
if (!this.initializePromise) {
|
||||
this.initializePromise = (async () => {
|
||||
try {
|
||||
|
||||
// Create storage via subclasses
|
||||
const storage = await this.doCreate();
|
||||
|
||||
// Replace our in-memory storage with the real
|
||||
// once as soon as possible without awaiting
|
||||
// the init call.
|
||||
this.storage.dispose();
|
||||
this.storage = storage;
|
||||
|
||||
// Re-emit storage changes via event
|
||||
this._register(storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
|
||||
|
||||
// Await storage init
|
||||
await this.doInit(storage);
|
||||
|
||||
// Ensure we track wether storage is new or not
|
||||
const isNewStorage = storage.getBoolean(IS_NEW_KEY);
|
||||
if (isNewStorage === undefined) {
|
||||
storage.set(IS_NEW_KEY, true);
|
||||
} else if (isNewStorage) {
|
||||
storage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(`StorageMain#initialize(): Unable to init storage due to ${error}`);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return this.initializePromise;
|
||||
}
|
||||
|
||||
protected createLogginOptions(): ISQLiteStorageDatabaseLoggingOptions {
|
||||
return {
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined,
|
||||
logError: error => this.logService.error(error)
|
||||
};
|
||||
}
|
||||
|
||||
protected doInit(storage: IStorage): Promise<void> {
|
||||
return storage.init();
|
||||
}
|
||||
|
||||
protected abstract doCreate(): Promise<IStorage>;
|
||||
|
||||
get items(): Map<string, string> { return this.storage.items; }
|
||||
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
get(key: string, fallbackValue?: string): string | undefined {
|
||||
return this.storage.get(key, fallbackValue);
|
||||
}
|
||||
|
||||
set(key: string, value: string | boolean | number | undefined | null): Promise<void> {
|
||||
return this.storage.set(key, value);
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
return this.storage.delete(key);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// Ensure we are not accidentally leaving
|
||||
// a pending initialized storage behind in
|
||||
// case close() was called before init()
|
||||
// finishes
|
||||
if (this.initializePromise) {
|
||||
await this.initializePromise;
|
||||
}
|
||||
|
||||
// Propagate to storage lib
|
||||
await this.storage.close();
|
||||
|
||||
// Signal as event
|
||||
this._onDidCloseStorage.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalStorageMain extends BaseStorageMain implements IStorageMain {
|
||||
|
||||
private static readonly STORAGE_NAME = 'state.vscdb';
|
||||
|
||||
constructor(
|
||||
private readonly options: IStorageMainOptions,
|
||||
logService: ILogService,
|
||||
private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super(logService);
|
||||
}
|
||||
|
||||
protected async doCreate(): Promise<IStorage> {
|
||||
let storagePath: string;
|
||||
if (this.options.useInMemoryStorage) {
|
||||
storagePath = SQLiteStorageDatabase.IN_MEMORY_PATH;
|
||||
} else {
|
||||
storagePath = join(this.environmentService.globalStorageHome.fsPath, GlobalStorageMain.STORAGE_NAME);
|
||||
}
|
||||
|
||||
return new Storage(new SQLiteStorageDatabase(storagePath, {
|
||||
logging: this.createLogginOptions()
|
||||
}));
|
||||
}
|
||||
|
||||
protected async doInit(storage: IStorage): Promise<void> {
|
||||
await super.doInit(storage);
|
||||
|
||||
// Apply global telemetry values as part of the initialization
|
||||
this.updateTelemetryState(storage);
|
||||
}
|
||||
|
||||
private updateTelemetryState(storage: IStorage): void {
|
||||
|
||||
// Instance UUID (once)
|
||||
const instanceId = storage.get(instanceStorageKey, undefined);
|
||||
if (instanceId === undefined) {
|
||||
storage.set(instanceStorageKey, generateUuid());
|
||||
}
|
||||
|
||||
// First session date (once)
|
||||
const firstSessionDate = storage.get(firstSessionDateStorageKey, undefined);
|
||||
if (firstSessionDate === undefined) {
|
||||
storage.set(firstSessionDateStorageKey, new Date().toUTCString());
|
||||
}
|
||||
|
||||
// Last / current session (always)
|
||||
// previous session date was the "current" one at that time
|
||||
// current session date is "now"
|
||||
const lastSessionDate = storage.get(currentSessionDateStorageKey, undefined);
|
||||
const currentSessionDate = new Date().toUTCString();
|
||||
storage.set(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate);
|
||||
storage.set(currentSessionDateStorageKey, currentSessionDate);
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMain {
|
||||
|
||||
private static readonly WORKSPACE_STORAGE_NAME = 'state.vscdb';
|
||||
private static readonly WORKSPACE_META_NAME = 'workspace.json';
|
||||
|
||||
constructor(
|
||||
private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier,
|
||||
private readonly options: IStorageMainOptions,
|
||||
logService: ILogService,
|
||||
private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super(logService);
|
||||
}
|
||||
|
||||
protected async doCreate(): Promise<IStorage> {
|
||||
const { storageFilePath, wasCreated } = await this.prepareWorkspaceStorageFolder();
|
||||
|
||||
return new Storage(new SQLiteStorageDatabase(storageFilePath, {
|
||||
logging: this.createLogginOptions()
|
||||
}), { hint: wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined });
|
||||
}
|
||||
|
||||
private async prepareWorkspaceStorageFolder(): Promise<{ storageFilePath: string, wasCreated: boolean }> {
|
||||
|
||||
// Return early if using inMemory storage
|
||||
if (this.options.useInMemoryStorage) {
|
||||
return { storageFilePath: SQLiteStorageDatabase.IN_MEMORY_PATH, wasCreated: true };
|
||||
}
|
||||
|
||||
// Otherwise, ensure the storage folder exists on disk
|
||||
const workspaceStorageFolderPath = join(this.environmentService.workspaceStorageHome.fsPath, this.workspace.id);
|
||||
const workspaceStorageDatabasePath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_STORAGE_NAME);
|
||||
|
||||
const storageExists = await exists(workspaceStorageFolderPath);
|
||||
if (storageExists) {
|
||||
return { storageFilePath: workspaceStorageDatabasePath, wasCreated: false };
|
||||
}
|
||||
|
||||
// Ensure storage folder exists
|
||||
await promises.mkdir(workspaceStorageFolderPath, { recursive: true });
|
||||
|
||||
// Write metadata into folder (but do not await)
|
||||
this.ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath);
|
||||
|
||||
return { storageFilePath: workspaceStorageDatabasePath, wasCreated: true };
|
||||
}
|
||||
|
||||
private async ensureWorkspaceStorageFolderMeta(workspaceStorageFolderPath: string): Promise<void> {
|
||||
let meta: object | undefined = undefined;
|
||||
if (isSingleFolderWorkspaceIdentifier(this.workspace)) {
|
||||
meta = { folder: this.workspace.uri.toString() };
|
||||
} else if (isWorkspaceIdentifier(this.workspace)) {
|
||||
meta = { workspace: this.workspace.configPath.toString() };
|
||||
}
|
||||
|
||||
if (meta) {
|
||||
try {
|
||||
const workspaceStorageMetaPath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_META_NAME);
|
||||
const storageExists = await exists(workspaceStorageMetaPath);
|
||||
if (!storageExists) {
|
||||
await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(`StorageMain#ensureWorkspaceStorageFolderMeta(): Unable to create workspace storage metadata due to ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { GlobalStorageMain, IStorageMain, IStorageMainOptions, WorkspaceStorageMain } from 'vs/platform/storage/electron-main/storageMain';
|
||||
import { IWindowSettings } from 'vs/platform/windows/common/windows';
|
||||
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
|
||||
|
||||
export interface IStorageMainService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Provides access to the global storage shared across all windows.
|
||||
*/
|
||||
readonly globalStorage: IStorageMain;
|
||||
|
||||
/**
|
||||
* Provides access to the workspace storage specific to a single window.
|
||||
*/
|
||||
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain;
|
||||
}
|
||||
|
||||
export class StorageMainService extends Disposable implements IStorageMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected getStorageOptions(): IStorageMainOptions {
|
||||
return {
|
||||
useInMemoryStorage: !!this.environmentService.extensionTestsLocationURI // no storage during extension tests!
|
||||
};
|
||||
}
|
||||
|
||||
protected enableMainWorkspaceStorage(): boolean {
|
||||
return !!(this.configurationService.getValue<IWindowSettings | undefined>('window')?.enableExperimentalMainProcessWorkspaceStorage);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Global Storage: Warmup when any window opens
|
||||
(async () => {
|
||||
await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
|
||||
|
||||
this.globalStorage.init();
|
||||
})();
|
||||
|
||||
// Workspace Storage: Warmup when related window with workspace loads
|
||||
if (this.enableMainWorkspaceStorage()) {
|
||||
this._register(this.lifecycleMainService.onWillLoadWindow(async e => {
|
||||
if (e.workspace) {
|
||||
this.workspaceStorage(e.workspace).init();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// All Storage: Close when shutting down
|
||||
this._register(this.lifecycleMainService.onWillShutdown(e => {
|
||||
|
||||
// Global Storage
|
||||
e.join(this.globalStorage.close());
|
||||
|
||||
// Workspace Storage(s)
|
||||
for (const [, storage] of this.mapWorkspaceToStorage) {
|
||||
e.join(storage.close());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
//#region Global Storage
|
||||
|
||||
readonly globalStorage = this.createGlobalStorage();
|
||||
|
||||
private createGlobalStorage(): IStorageMain {
|
||||
if (this.globalStorage) {
|
||||
return this.globalStorage; // only once
|
||||
}
|
||||
|
||||
this.logService.trace(`StorageMainService: creating global storage`);
|
||||
|
||||
const globalStorage = new GlobalStorageMain(this.getStorageOptions(), this.logService, this.environmentService);
|
||||
|
||||
once(globalStorage.onDidCloseStorage)(() => {
|
||||
this.logService.trace(`StorageMainService: closed global storage`);
|
||||
});
|
||||
|
||||
return globalStorage;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Workspace Storage
|
||||
|
||||
private readonly mapWorkspaceToStorage = new Map<string, IStorageMain>();
|
||||
|
||||
private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain {
|
||||
const workspaceStorage = new WorkspaceStorageMain(workspace, this.getStorageOptions(), this.logService, this.environmentService);
|
||||
|
||||
return workspaceStorage;
|
||||
}
|
||||
|
||||
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorageMain {
|
||||
let workspaceStorage = this.mapWorkspaceToStorage.get(workspace.id);
|
||||
if (!workspaceStorage) {
|
||||
this.logService.trace(`StorageMainService: creating workspace storage (${workspace.id})`);
|
||||
|
||||
workspaceStorage = this.createWorkspaceStorage(workspace);
|
||||
this.mapWorkspaceToStorage.set(workspace.id, workspaceStorage);
|
||||
|
||||
once(workspaceStorage.onDidCloseStorage)(() => {
|
||||
this.logService.trace(`StorageMainService: closed workspace storage (${workspace.id})`);
|
||||
|
||||
this.mapWorkspaceToStorage.delete(workspace.id);
|
||||
});
|
||||
}
|
||||
|
||||
return workspaceStorage;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { StorageScope, WillSaveStateReason, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Storage, IStorage } from 'vs/base/parts/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IEmptyWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { StorageDatabaseChannelClient } from 'vs/platform/storage/common/storageIpc';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
|
||||
export class NativeStorageService2 extends AbstractStorageService {
|
||||
|
||||
// Global Storage is readonly and shared across windows
|
||||
private readonly globalStorage: IStorage;
|
||||
|
||||
// Workspace Storage is scoped to a window but can change
|
||||
// in the current window, when entering a workspace!
|
||||
private workspaceStorage: IStorage | undefined = undefined;
|
||||
private workspaceStorageId: string | undefined = undefined;
|
||||
private workspaceStorageDisposable = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined,
|
||||
private readonly mainProcessService: IMainProcessService,
|
||||
private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.globalStorage = this.createGlobalStorage();
|
||||
this.workspaceStorage = this.createWorkspaceStorage(workspace);
|
||||
}
|
||||
|
||||
private createGlobalStorage(): IStorage {
|
||||
const storageDataBaseClient = new StorageDatabaseChannelClient(this.mainProcessService.getChannel('storage'), undefined);
|
||||
|
||||
const globalStorage = new Storage(storageDataBaseClient.globalStorage);
|
||||
|
||||
this._register(globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
|
||||
return globalStorage;
|
||||
}
|
||||
|
||||
private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier): IStorage;
|
||||
private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IStorage | undefined;
|
||||
private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IEmptyWorkspaceIdentifier | undefined): IStorage | undefined {
|
||||
const storageDataBaseClient = new StorageDatabaseChannelClient(this.mainProcessService.getChannel('storage'), workspace);
|
||||
|
||||
if (storageDataBaseClient.workspaceStorage) {
|
||||
const workspaceStorage = new Storage(storageDataBaseClient.workspaceStorage);
|
||||
|
||||
this.workspaceStorageDisposable.value = workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key));
|
||||
this.workspaceStorageId = workspace?.id;
|
||||
|
||||
return workspaceStorage;
|
||||
} else {
|
||||
this.workspaceStorageDisposable.clear();
|
||||
this.workspaceStorageId = undefined;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected async doInitialize(): Promise<void> {
|
||||
|
||||
// Init all storage locations
|
||||
mark('code/willInitStorage');
|
||||
try {
|
||||
await Promises.settled([
|
||||
this.globalStorage.init(),
|
||||
this.workspaceStorage?.init() ?? Promise.resolve()
|
||||
]);
|
||||
} finally {
|
||||
mark('code/didInitStorage');
|
||||
}
|
||||
}
|
||||
|
||||
protected getStorage(scope: StorageScope): IStorage | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
|
||||
}
|
||||
|
||||
protected getLogDetails(scope: StorageScope): string | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.environmentService.globalStorageHome.fsPath : this.workspaceStorageId ? `${joinPath(this.environmentService.workspaceStorageHome, this.workspaceStorageId, 'state.vscdb').fsPath} [!!! Experimental Main Storage !!!]` : undefined;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// Stop periodic scheduler and idle runner as we now collect state normally
|
||||
this.stopFlushWhenIdle();
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
|
||||
|
||||
// Do it
|
||||
await Promises.settled([
|
||||
this.globalStorage.close(),
|
||||
this.workspaceStorage?.close() ?? Promise.resolve()
|
||||
]);
|
||||
}
|
||||
|
||||
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
|
||||
// Keep current workspace storage items around to restore
|
||||
const oldWorkspaceStorage = this.workspaceStorage;
|
||||
const oldItems = oldWorkspaceStorage?.items ?? new Map();
|
||||
|
||||
// Close current which will change to new workspace storage
|
||||
if (oldWorkspaceStorage) {
|
||||
await oldWorkspaceStorage.close();
|
||||
oldWorkspaceStorage.dispose();
|
||||
}
|
||||
|
||||
// Create new workspace storage & init
|
||||
this.workspaceStorage = this.createWorkspaceStorage(toWorkspace);
|
||||
await this.workspaceStorage.init();
|
||||
|
||||
// Copy over previous keys
|
||||
for (const [key, value] of oldItems) {
|
||||
this.workspaceStorage.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +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 { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IStorageChangeEvent, IStorageMainService } from 'vs/platform/storage/node/storageMainService';
|
||||
import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type Key = string;
|
||||
type Value = string;
|
||||
type Item = [Key, Value];
|
||||
|
||||
interface ISerializableUpdateRequest {
|
||||
insert?: Item[];
|
||||
delete?: Key[];
|
||||
}
|
||||
|
||||
interface ISerializableItemsChangeEvent {
|
||||
readonly changed?: Item[];
|
||||
readonly deleted?: Key[];
|
||||
}
|
||||
|
||||
export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel {
|
||||
|
||||
private static readonly STORAGE_CHANGE_DEBOUNCE_TIME = 100;
|
||||
|
||||
private readonly _onDidChangeItems = this._register(new Emitter<ISerializableItemsChangeEvent>());
|
||||
readonly onDidChangeItems = this._onDidChangeItems.event;
|
||||
|
||||
private readonly whenReady = this.init();
|
||||
|
||||
constructor(
|
||||
private logService: ILogService,
|
||||
private storageMainService: IStorageMainService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private async init(): Promise<void> {
|
||||
try {
|
||||
await this.storageMainService.initialize();
|
||||
} catch (error) {
|
||||
this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`);
|
||||
}
|
||||
|
||||
// Apply global telemetry values as part of the initialization
|
||||
// These are global across all windows and thereby should be
|
||||
// written from the main process once.
|
||||
this.initTelemetry();
|
||||
|
||||
// Setup storage change listeners
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private initTelemetry(): void {
|
||||
const instanceId = this.storageMainService.get(instanceStorageKey, undefined);
|
||||
if (instanceId === undefined) {
|
||||
this.storageMainService.store(instanceStorageKey, generateUuid());
|
||||
}
|
||||
|
||||
const firstSessionDate = this.storageMainService.get(firstSessionDateStorageKey, undefined);
|
||||
if (firstSessionDate === undefined) {
|
||||
this.storageMainService.store(firstSessionDateStorageKey, new Date().toUTCString());
|
||||
}
|
||||
|
||||
const lastSessionDate = this.storageMainService.get(currentSessionDateStorageKey, undefined); // previous session date was the "current" one at that time
|
||||
const currentSessionDate = new Date().toUTCString(); // current session date is "now"
|
||||
this.storageMainService.store(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate);
|
||||
this.storageMainService.store(currentSessionDateStorageKey, currentSessionDate);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Listen for changes in global storage to send to listeners
|
||||
// that are listening. Use a debouncer to reduce IPC traffic.
|
||||
this._register(Event.debounce(this.storageMainService.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => {
|
||||
if (!prev) {
|
||||
prev = [cur];
|
||||
} else {
|
||||
prev.push(cur);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, GlobalStorageDatabaseChannel.STORAGE_CHANGE_DEBOUNCE_TIME)(events => {
|
||||
if (events.length) {
|
||||
this._onDidChangeItems.fire(this.serializeEvents(events));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
|
||||
const changed = new Map<Key, Value>();
|
||||
const deleted = new Set<Key>();
|
||||
events.forEach(event => {
|
||||
const existing = this.storageMainService.get(event.key);
|
||||
if (typeof existing === 'string') {
|
||||
changed.set(event.key, existing);
|
||||
} else {
|
||||
deleted.add(event.key);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
changed: Array.from(changed.entries()),
|
||||
deleted: Array.from(deleted.values())
|
||||
};
|
||||
}
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeItems': return this.onDidChangeItems;
|
||||
}
|
||||
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
async call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
|
||||
// ensure to always wait for ready
|
||||
await this.whenReady;
|
||||
|
||||
// handle call
|
||||
switch (command) {
|
||||
case 'getItems': {
|
||||
return Array.from(this.storageMainService.items.entries());
|
||||
}
|
||||
|
||||
case 'updateItems': {
|
||||
const items: ISerializableUpdateRequest = arg;
|
||||
if (items.insert) {
|
||||
for (const [key, value] of items.insert) {
|
||||
this.storageMainService.store(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (items.delete) {
|
||||
items.delete.forEach(key => this.storageMainService.remove(key));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Call not found: ${command}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalStorageDatabaseChannelClient extends Disposable implements IStorageDatabase {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
|
||||
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
|
||||
|
||||
private onDidChangeItemsOnMainListener: IDisposable | undefined;
|
||||
|
||||
constructor(private channel: IChannel) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.onDidChangeItemsOnMainListener = this.channel.listen<ISerializableItemsChangeEvent>('onDidChangeItems')((e: ISerializableItemsChangeEvent) => this.onDidChangeItemsOnMain(e));
|
||||
}
|
||||
|
||||
private onDidChangeItemsOnMain(e: ISerializableItemsChangeEvent): void {
|
||||
if (Array.isArray(e.changed) || Array.isArray(e.deleted)) {
|
||||
this._onDidChangeItemsExternal.fire({
|
||||
changed: e.changed ? new Map(e.changed) : undefined,
|
||||
deleted: e.deleted ? new Set<string>(e.deleted) : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getItems(): Promise<Map<string, string>> {
|
||||
const items: Item[] = await this.channel.call('getItems');
|
||||
|
||||
return new Map(items);
|
||||
}
|
||||
|
||||
updateItems(request: IUpdateRequest): Promise<void> {
|
||||
const serializableRequest: ISerializableUpdateRequest = Object.create(null);
|
||||
|
||||
if (request.insert) {
|
||||
serializableRequest.insert = Array.from(request.insert.entries());
|
||||
}
|
||||
|
||||
if (request.delete) {
|
||||
serializableRequest.delete = Array.from(request.delete.values());
|
||||
}
|
||||
|
||||
return this.channel.call('updateItems', serializableRequest);
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// when we are about to close, we start to ignore main-side changes since we close anyway
|
||||
dispose(this.onDidChangeItemsOnMainListener);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
dispose(this.onDidChangeItemsOnMainListener);
|
||||
}
|
||||
}
|
||||
@@ -1,191 +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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorage, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
|
||||
|
||||
export interface IStorageMainService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Emitted whenever data is updated or deleted.
|
||||
*/
|
||||
readonly onDidChangeStorage: Event<IStorageChangeEvent>;
|
||||
|
||||
/**
|
||||
* Emitted when the storage is about to persist. This is the right time
|
||||
* to persist data to ensure it is stored before the application shuts
|
||||
* down.
|
||||
*
|
||||
* Note: this event may be fired many times, not only on shutdown to prevent
|
||||
* loss of state in situations where the shutdown is not sufficient to
|
||||
* persist the data properly.
|
||||
*/
|
||||
readonly onWillSaveState: Event<void>;
|
||||
|
||||
/**
|
||||
* Access to all cached items of this storage service.
|
||||
*/
|
||||
readonly items: Map<string, string>;
|
||||
|
||||
/**
|
||||
* Required call to ensure the service can be used.
|
||||
*/
|
||||
initialize(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined.
|
||||
*/
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a boolean.
|
||||
*/
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
|
||||
|
||||
/**
|
||||
* Retrieve an element stored with the given key from storage. Use
|
||||
* the provided defaultValue if the element is null or undefined. The element
|
||||
* will be converted to a number using parseInt with a base of 10.
|
||||
*/
|
||||
getNumber(key: string, fallbackValue: number): number;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined;
|
||||
|
||||
/**
|
||||
* Store a string value under the given key to storage. The value will
|
||||
* be converted to a string.
|
||||
*/
|
||||
store(key: string, value: string | boolean | number | undefined | null): void;
|
||||
|
||||
/**
|
||||
* Delete an element stored under the provided key from storage.
|
||||
*/
|
||||
remove(key: string): void;
|
||||
}
|
||||
|
||||
export interface IStorageChangeEvent {
|
||||
key: string;
|
||||
}
|
||||
|
||||
export class StorageMainService extends Disposable implements IStorageMainService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static readonly STORAGE_NAME = 'state.vscdb';
|
||||
|
||||
private readonly _onDidChangeStorage = this._register(new Emitter<IStorageChangeEvent>());
|
||||
readonly onDidChangeStorage = this._onDidChangeStorage.event;
|
||||
|
||||
private readonly _onWillSaveState = this._register(new Emitter<void>());
|
||||
readonly onWillSaveState = this._onWillSaveState.event;
|
||||
|
||||
get items(): Map<string, string> { return this.storage.items; }
|
||||
|
||||
private storage: IStorage;
|
||||
|
||||
private initializePromise: Promise<void> | undefined;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Until the storage has been initialized, it can only be in memory
|
||||
this.storage = new Storage(new InMemoryStorageDatabase());
|
||||
}
|
||||
|
||||
private get storagePath(): string {
|
||||
if (!!this.environmentService.extensionTestsLocationURI) {
|
||||
return SQLiteStorageDatabase.IN_MEMORY_PATH; // no storage during extension tests!
|
||||
}
|
||||
|
||||
return join(this.environmentService.globalStorageHome.fsPath, StorageMainService.STORAGE_NAME);
|
||||
}
|
||||
|
||||
private createLogginOptions(): ISQLiteStorageDatabaseLoggingOptions {
|
||||
return {
|
||||
logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined,
|
||||
logError: error => this.logService.error(error)
|
||||
};
|
||||
}
|
||||
|
||||
initialize(): Promise<void> {
|
||||
if (!this.initializePromise) {
|
||||
this.initializePromise = this.doInitialize();
|
||||
}
|
||||
|
||||
return this.initializePromise;
|
||||
}
|
||||
|
||||
private async doInitialize(): Promise<void> {
|
||||
this.storage.dispose();
|
||||
this.storage = new Storage(new SQLiteStorageDatabase(this.storagePath, {
|
||||
logging: this.createLogginOptions()
|
||||
}));
|
||||
|
||||
this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key })));
|
||||
|
||||
await this.storage.init();
|
||||
|
||||
// Check to see if this is the first time we are "opening" the application
|
||||
const firstOpen = this.storage.getBoolean(IS_NEW_KEY);
|
||||
if (firstOpen === undefined) {
|
||||
this.storage.set(IS_NEW_KEY, true);
|
||||
} else if (firstOpen) {
|
||||
this.storage.set(IS_NEW_KEY, false);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string, fallbackValue: string): string;
|
||||
get(key: string, fallbackValue?: string): string | undefined;
|
||||
get(key: string, fallbackValue?: string): string | undefined {
|
||||
return this.storage.get(key, fallbackValue);
|
||||
}
|
||||
|
||||
getBoolean(key: string, fallbackValue: boolean): boolean;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined;
|
||||
getBoolean(key: string, fallbackValue?: boolean): boolean | undefined {
|
||||
return this.storage.getBoolean(key, fallbackValue);
|
||||
}
|
||||
|
||||
getNumber(key: string, fallbackValue: number): number;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined;
|
||||
getNumber(key: string, fallbackValue?: number): number | undefined {
|
||||
return this.storage.getNumber(key, fallbackValue);
|
||||
}
|
||||
|
||||
store(key: string, value: string | boolean | number | undefined | null): Promise<void> {
|
||||
return this.storage.set(key, value);
|
||||
}
|
||||
|
||||
remove(key: string): Promise<void> {
|
||||
return this.storage.delete(key);
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this._onWillSaveState.fire();
|
||||
|
||||
// Do it
|
||||
return this.storage.close();
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,19 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { promises } from 'fs';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
|
||||
import { StorageScope, WillSaveStateReason, logStorage, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, WillSaveStateReason, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorageDatabase, IStorage, StorageHint } from 'vs/base/parts/storage/common/storage';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs';
|
||||
import { copy, exists, writeFile } from 'vs/base/node/pfs';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
|
||||
export class NativeStorageService extends AbstractStorageService {
|
||||
|
||||
@@ -27,13 +28,9 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
private workspaceStorage: IStorage | undefined;
|
||||
private workspaceStorageListener: IDisposable | undefined;
|
||||
|
||||
private initializePromise: Promise<void> | undefined;
|
||||
|
||||
private readonly periodicFlushScheduler = this._register(new RunOnceScheduler(() => this.doFlushWhenIdle(), 60000 /* every minute */));
|
||||
private runWhenIdleDisposable: IDisposable | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
private globalStorageDatabase: IStorageDatabase,
|
||||
private payload: IWorkspaceInitializationPayload | undefined,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
) {
|
||||
@@ -48,26 +45,13 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
|
||||
}
|
||||
|
||||
initialize(payload?: IWorkspaceInitializationPayload): Promise<void> {
|
||||
if (!this.initializePromise) {
|
||||
this.initializePromise = this.doInitialize(payload);
|
||||
}
|
||||
|
||||
return this.initializePromise;
|
||||
}
|
||||
|
||||
private async doInitialize(payload?: IWorkspaceInitializationPayload): Promise<void> {
|
||||
protected async doInitialize(): Promise<void> {
|
||||
|
||||
// Init all storage locations
|
||||
await Promise.all([
|
||||
await Promises.settled([
|
||||
this.initializeGlobalStorage(),
|
||||
payload ? this.initializeWorkspaceStorage(payload) : Promise.resolve()
|
||||
this.payload ? this.initializeWorkspaceStorage(this.payload) : Promise.resolve()
|
||||
]);
|
||||
|
||||
// On some OS we do not get enough time to persist state on shutdown (e.g. when
|
||||
// Windows restarts after applying updates). In other cases, VSCode might crash,
|
||||
// so we periodically save state to reduce the chance of loosing any state.
|
||||
this.periodicFlushScheduler.schedule();
|
||||
}
|
||||
|
||||
private initializeGlobalStorage(): Promise<void> {
|
||||
@@ -138,7 +122,7 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
return { path: workspaceStorageFolderPath, wasCreated: false };
|
||||
}
|
||||
|
||||
await mkdirp(workspaceStorageFolderPath);
|
||||
await promises.mkdir(workspaceStorageFolderPath, { recursive: true });
|
||||
|
||||
// Write metadata into folder
|
||||
this.ensureWorkspaceStorageFolderMeta(payload);
|
||||
@@ -169,6 +153,7 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
get(key: string, scope: StorageScope, fallbackValue: string): string;
|
||||
get(key: string, scope: StorageScope): string | undefined;
|
||||
get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined {
|
||||
@@ -193,73 +178,38 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
|
||||
protected doRemove(key: string, scope: StorageScope): void {
|
||||
this.getStorage(scope).delete(key);
|
||||
=======
|
||||
protected getStorage(scope: StorageScope): IStorage | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage;
|
||||
>>>>>>> e8cd17a97d8c58fffcbac05394b3ee2b3c72d384
|
||||
}
|
||||
|
||||
private getStorage(scope: StorageScope): IStorage {
|
||||
return assertIsDefined(scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage);
|
||||
}
|
||||
|
||||
protected async doFlush(): Promise<void> {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
if (this.globalStorage) {
|
||||
promises.push(this.globalStorage.whenFlushed());
|
||||
}
|
||||
|
||||
if (this.workspaceStorage) {
|
||||
promises.push(this.workspaceStorage.whenFlushed());
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private doFlushWhenIdle(): void {
|
||||
|
||||
// Dispose any previous idle runner
|
||||
dispose(this.runWhenIdleDisposable);
|
||||
|
||||
// Run when idle
|
||||
this.runWhenIdleDisposable = runWhenIdle(() => {
|
||||
|
||||
// send event to collect state
|
||||
this.flush();
|
||||
|
||||
// repeat
|
||||
this.periodicFlushScheduler.schedule();
|
||||
});
|
||||
protected getLogDetails(scope: StorageScope): string | undefined {
|
||||
return scope === StorageScope.GLOBAL ? this.environmentService.globalStorageHome.fsPath : this.workspaceStoragePath;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
|
||||
// Stop periodic scheduler and idle runner as we now collect state normally
|
||||
this.periodicFlushScheduler.dispose();
|
||||
dispose(this.runWhenIdleDisposable);
|
||||
this.runWhenIdleDisposable = undefined;
|
||||
this.stopFlushWhenIdle();
|
||||
|
||||
// Signal as event so that clients can still store data
|
||||
this.emitWillSaveState(WillSaveStateReason.SHUTDOWN);
|
||||
|
||||
// Do it
|
||||
await Promise.all([
|
||||
await Promises.settled([
|
||||
this.globalStorage.close(),
|
||||
this.workspaceStorage ? this.workspaceStorage.close() : Promise.resolve()
|
||||
]);
|
||||
}
|
||||
|
||||
async logStorage(): Promise<void> {
|
||||
return logStorage(
|
||||
this.globalStorage.items,
|
||||
this.workspaceStorage ? this.workspaceStorage.items : new Map<string, string>(), // Shared process storage does not has workspace storage
|
||||
this.environmentService.globalStorageHome.fsPath,
|
||||
this.workspaceStoragePath || '');
|
||||
}
|
||||
|
||||
async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise<void> {
|
||||
if (this.workspaceStoragePath === SQLiteStorageDatabase.IN_MEMORY_PATH) {
|
||||
return; // no migration needed if running in memory
|
||||
}
|
||||
|
||||
// Close workspace DB to be able to copy
|
||||
await this.getStorage(StorageScope.WORKSPACE).close();
|
||||
await this.workspaceStorage?.close();
|
||||
|
||||
// Prepare new workspace storage folder
|
||||
const result = await this.prepareWorkspaceStorageFolder(toWorkspace);
|
||||
@@ -267,7 +217,7 @@ export class NativeStorageService extends AbstractStorageService {
|
||||
const newWorkspaceStoragePath = join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME);
|
||||
|
||||
// Copy current storage over to new workspace storage
|
||||
await copy(assertIsDefined(this.workspaceStoragePath), newWorkspaceStoragePath);
|
||||
await copy(assertIsDefined(this.workspaceStoragePath), newWorkspaceStoragePath, { preserveSymlinks: false });
|
||||
|
||||
// Recreate and init workspace storage
|
||||
return this.createWorkspaceStorage(newWorkspaceStoragePath).init();
|
||||
|
||||
@@ -4,49 +4,67 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual } from 'assert';
|
||||
import { FileStorageDatabase } from 'vs/platform/storage/browser/storageService';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { rimraf } from 'vs/base/node/pfs';
|
||||
import { BrowserStorageService, FileStorageDatabase } from 'vs/platform/storage/browser/storageService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { Storage } from 'vs/base/parts/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
|
||||
import { createSuite } from 'vs/platform/storage/test/common/storageService.test';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
suite('Storage', () => {
|
||||
suite('StorageService (browser)', function () {
|
||||
|
||||
let testDir: string;
|
||||
const disposables = new DisposableStore();
|
||||
let storageService: BrowserStorageService;
|
||||
|
||||
createSuite<BrowserStorageService>({
|
||||
setup: async () => {
|
||||
const logService = new NullLogService();
|
||||
|
||||
const fileService = disposables.add(new FileService(logService));
|
||||
|
||||
const userDataProvider = disposables.add(new InMemoryFileSystemProvider());
|
||||
disposables.add(fileService.registerProvider(Schemas.userData, userDataProvider));
|
||||
|
||||
storageService = disposables.add(new BrowserStorageService({ id: String(Date.now()) }, { userRoamingDataHome: URI.file('/User').with({ scheme: Schemas.userData }) } as unknown as IEnvironmentService, fileService));
|
||||
|
||||
await storageService.initialize();
|
||||
|
||||
return storageService;
|
||||
},
|
||||
teardown: async storage => {
|
||||
await storageService.flush();
|
||||
disposables.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('FileStorageDatabase (browser)', () => {
|
||||
|
||||
let fileService: FileService;
|
||||
let fileProvider: DiskFileSystemProvider;
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
setup(async () => {
|
||||
const logService = new NullLogService();
|
||||
|
||||
fileService = new FileService(logService);
|
||||
disposables.add(fileService);
|
||||
fileService = disposables.add(new FileService(logService));
|
||||
|
||||
fileProvider = new DiskFileSystemProvider(logService);
|
||||
disposables.add(fileService.registerProvider(Schemas.file, fileProvider));
|
||||
disposables.add(fileProvider);
|
||||
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice');
|
||||
const userDataProvider = disposables.add(new InMemoryFileSystemProvider());
|
||||
disposables.add(fileService.registerProvider(Schemas.userData, userDataProvider));
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
disposables.clear();
|
||||
|
||||
return rimraf(testDir);
|
||||
});
|
||||
|
||||
test('File Based Storage', async () => {
|
||||
let storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService));
|
||||
test('Basics', async () => {
|
||||
const testDir = URI.file('/User/storage.json').with({ scheme: Schemas.userData });
|
||||
|
||||
let storage = new Storage(new FileStorageDatabase(testDir, false, fileService));
|
||||
|
||||
await storage.init();
|
||||
|
||||
@@ -60,7 +78,7 @@ suite('Storage', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService));
|
||||
storage = new Storage(new FileStorageDatabase(testDir, false, fileService));
|
||||
|
||||
await storage.init();
|
||||
|
||||
@@ -78,7 +96,7 @@ suite('Storage', () => {
|
||||
|
||||
await storage.close();
|
||||
|
||||
storage = new Storage(new FileStorageDatabase(URI.file(join(testDir, 'storage.json')), false, fileService));
|
||||
storage = new Storage(new FileStorageDatabase(testDir, false, fileService));
|
||||
|
||||
await storage.init();
|
||||
|
||||
@@ -4,98 +4,102 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { strictEqual, ok } from 'assert';
|
||||
import { StorageScope, InMemoryStorageService, StorageTarget, IStorageValueChangeEvent, IStorageTargetChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { StorageScope, InMemoryStorageService, StorageTarget, IStorageValueChangeEvent, IStorageTargetChangeEvent, IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
suite('StorageService', function () {
|
||||
export function createSuite<T extends IStorageService>(params: { setup: () => Promise<T>, teardown: (service: T) => Promise<void> }): void {
|
||||
|
||||
test('Get Data, Integer, Boolean (global, in-memory)', () => {
|
||||
let storageService: T;
|
||||
|
||||
setup(async () => {
|
||||
storageService = await params.setup();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
return params.teardown(storageService);
|
||||
});
|
||||
|
||||
test('Get Data, Integer, Boolean (global)', () => {
|
||||
storeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Get Data, Integer, Boolean (workspace, in-memory)', () => {
|
||||
test('Get Data, Integer, Boolean (workspace)', () => {
|
||||
storeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function storeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageValueChangeEvents: IStorageValueChangeEvent[] = [];
|
||||
storage.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
storageService.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
|
||||
strictEqual(storage.get('test.get', scope, 'foobar'), 'foobar');
|
||||
strictEqual(storage.get('test.get', scope, ''), '');
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 5), 5);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, 0), 0);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, true), true);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, false), false);
|
||||
strictEqual(storageService.get('test.get', scope, 'foobar'), 'foobar');
|
||||
strictEqual(storageService.get('test.get', scope, ''), '');
|
||||
strictEqual(storageService.getNumber('test.getNumber', scope, 5), 5);
|
||||
strictEqual(storageService.getNumber('test.getNumber', scope, 0), 0);
|
||||
strictEqual(storageService.getBoolean('test.getBoolean', scope, true), true);
|
||||
strictEqual(storageService.getBoolean('test.getBoolean', scope, false), false);
|
||||
|
||||
storage.store('test.get', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), 'foobar');
|
||||
storageService.store('test.get', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.get('test.get', scope, (undefined)!), 'foobar');
|
||||
let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get');
|
||||
strictEqual(storageValueChangeEvent?.scope, scope);
|
||||
strictEqual(storageValueChangeEvent?.key, 'test.get');
|
||||
storageValueChangeEvents = [];
|
||||
|
||||
storage.store('test.get', '', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.get('test.get', scope, (undefined)!), '');
|
||||
storageService.store('test.get', '', scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.get('test.get', scope, (undefined)!), '');
|
||||
storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.get');
|
||||
strictEqual(storageValueChangeEvent!.scope, scope);
|
||||
strictEqual(storageValueChangeEvent!.key, 'test.get');
|
||||
|
||||
storage.store('test.getNumber', 5, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 5);
|
||||
storageService.store('test.getNumber', 5, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.getNumber('test.getNumber', scope, (undefined)!), 5);
|
||||
|
||||
storage.store('test.getNumber', 0, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getNumber('test.getNumber', scope, (undefined)!), 0);
|
||||
storageService.store('test.getNumber', 0, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.getNumber('test.getNumber', scope, (undefined)!), 0);
|
||||
|
||||
storage.store('test.getBoolean', true, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), true);
|
||||
storageService.store('test.getBoolean', true, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.getBoolean('test.getBoolean', scope, (undefined)!), true);
|
||||
|
||||
storage.store('test.getBoolean', false, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storage.getBoolean('test.getBoolean', scope, (undefined)!), false);
|
||||
storageService.store('test.getBoolean', false, scope, StorageTarget.MACHINE);
|
||||
strictEqual(storageService.getBoolean('test.getBoolean', scope, (undefined)!), false);
|
||||
|
||||
strictEqual(storage.get('test.getDefault', scope, 'getDefault'), 'getDefault');
|
||||
strictEqual(storage.getNumber('test.getNumberDefault', scope, 5), 5);
|
||||
strictEqual(storage.getBoolean('test.getBooleanDefault', scope, true), true);
|
||||
strictEqual(storageService.get('test.getDefault', scope, 'getDefault'), 'getDefault');
|
||||
strictEqual(storageService.getNumber('test.getNumberDefault', scope, 5), 5);
|
||||
strictEqual(storageService.getBoolean('test.getBooleanDefault', scope, true), true);
|
||||
}
|
||||
|
||||
test('Remove Data (global, in-memory)', () => {
|
||||
test('Remove Data (global)', () => {
|
||||
removeData(StorageScope.GLOBAL);
|
||||
});
|
||||
|
||||
test('Remove Data (workspace, in-memory)', () => {
|
||||
test('Remove Data (workspace)', () => {
|
||||
removeData(StorageScope.WORKSPACE);
|
||||
});
|
||||
|
||||
function removeData(scope: StorageScope): void {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageValueChangeEvents: IStorageValueChangeEvent[] = [];
|
||||
storage.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
storageService.onDidChangeValue(e => storageValueChangeEvents.push(e));
|
||||
|
||||
storage.store('test.remove', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual('foobar', storage.get('test.remove', scope, (undefined)!));
|
||||
storageService.store('test.remove', 'foobar', scope, StorageTarget.MACHINE);
|
||||
strictEqual('foobar', storageService.get('test.remove', scope, (undefined)!));
|
||||
|
||||
storage.remove('test.remove', scope);
|
||||
ok(!storage.get('test.remove', scope, (undefined)!));
|
||||
storageService.remove('test.remove', scope);
|
||||
ok(!storageService.get('test.remove', scope, (undefined)!));
|
||||
let storageValueChangeEvent = storageValueChangeEvents.find(e => e.key === 'test.remove');
|
||||
strictEqual(storageValueChangeEvent?.scope, scope);
|
||||
strictEqual(storageValueChangeEvent?.key, 'test.remove');
|
||||
}
|
||||
|
||||
test('Keys (in-memory)', () => {
|
||||
const storage = new InMemoryStorageService();
|
||||
|
||||
let storageTargetEvent: IStorageTargetChangeEvent | undefined = undefined;
|
||||
storage.onDidChangeTarget(e => storageTargetEvent = e);
|
||||
storageService.onDidChangeTarget(e => storageTargetEvent = e);
|
||||
|
||||
let storageValueChangeEvent: IStorageValueChangeEvent | undefined = undefined;
|
||||
storage.onDidChangeValue(e => storageValueChangeEvent = e);
|
||||
storageService.onDidChangeValue(e => storageValueChangeEvent = e);
|
||||
|
||||
// Empty
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
strictEqual(storageService.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +109,8 @@ suite('StorageService', function () {
|
||||
storageTargetEvent = Object.create(null);
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
storageService.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 1);
|
||||
strictEqual(storageTargetEvent?.scope, scope);
|
||||
strictEqual(storageValueChangeEvent?.key, 'test.target1');
|
||||
strictEqual(storageValueChangeEvent?.scope, scope);
|
||||
@@ -115,33 +119,33 @@ suite('StorageService', function () {
|
||||
storageTargetEvent = undefined;
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', 'otherValue1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
storageService.store('test.target1', 'otherValue1', scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 1);
|
||||
strictEqual(storageTargetEvent, undefined);
|
||||
strictEqual(storageValueChangeEvent?.key, 'test.target1');
|
||||
strictEqual(storageValueChangeEvent?.scope, scope);
|
||||
strictEqual(storageValueChangeEvent?.target, target);
|
||||
|
||||
storage.store('test.target2', 'value2', scope, target);
|
||||
storage.store('test.target3', 'value3', scope, target);
|
||||
storageService.store('test.target2', 'value2', scope, target);
|
||||
storageService.store('test.target3', 'value3', scope, target);
|
||||
|
||||
strictEqual(storage.keys(scope, target).length, 3);
|
||||
strictEqual(storageService.keys(scope, target).length, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove values
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
const keysLength = storage.keys(scope, target).length;
|
||||
const keysLength = storageService.keys(scope, target).length;
|
||||
|
||||
storage.store('test.target4', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, keysLength + 1);
|
||||
storageService.store('test.target4', 'value1', scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, keysLength + 1);
|
||||
|
||||
storageTargetEvent = Object.create(null);
|
||||
storageValueChangeEvent = Object.create(null);
|
||||
|
||||
storage.remove('test.target4', scope);
|
||||
strictEqual(storage.keys(scope, target).length, keysLength);
|
||||
storageService.remove('test.target4', scope);
|
||||
strictEqual(storageService.keys(scope, target).length, keysLength);
|
||||
strictEqual(storageTargetEvent?.scope, scope);
|
||||
strictEqual(storageValueChangeEvent?.key, 'test.target4');
|
||||
strictEqual(storageValueChangeEvent?.scope, scope);
|
||||
@@ -151,48 +155,55 @@ suite('StorageService', function () {
|
||||
// Remove all
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
const keys = storage.keys(scope, target);
|
||||
const keys = storageService.keys(scope, target);
|
||||
|
||||
for (const key of keys) {
|
||||
storage.remove(key, scope);
|
||||
storageService.remove(key, scope);
|
||||
}
|
||||
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
strictEqual(storageService.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Adding undefined or null removes value
|
||||
for (const scope of [StorageScope.WORKSPACE, StorageScope.GLOBAL]) {
|
||||
for (const target of [StorageTarget.MACHINE, StorageTarget.USER]) {
|
||||
storage.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
storageService.store('test.target1', 'value1', scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 1);
|
||||
|
||||
storageTargetEvent = Object.create(null);
|
||||
|
||||
storage.store('test.target1', undefined, scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
storageService.store('test.target1', undefined, scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 0);
|
||||
strictEqual(storageTargetEvent?.scope, scope);
|
||||
|
||||
storage.store('test.target1', '', scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 1);
|
||||
storageService.store('test.target1', '', scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 1);
|
||||
|
||||
storage.store('test.target1', null, scope, target);
|
||||
strictEqual(storage.keys(scope, target).length, 0);
|
||||
storageService.store('test.target1', null, scope, target);
|
||||
strictEqual(storageService.keys(scope, target).length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Target change
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.USER);
|
||||
storageService.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.USER);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(storageTargetEvent);
|
||||
storageTargetEvent = undefined;
|
||||
storage.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
storageService.store('test.target5', 'value1', StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
ok(!storageTargetEvent); // no change in target
|
||||
});
|
||||
}
|
||||
|
||||
suite('StorageService (in-memory)', function () {
|
||||
createSuite<InMemoryStorageService>({
|
||||
setup: async () => new InMemoryStorageService(),
|
||||
teardown: async () => { }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { notStrictEqual, strictEqual } from 'assert';
|
||||
import { OPTIONS, parseArgs } from 'vs/platform/environment/node/argv';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { StorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
|
||||
import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageChangeEvent, IStorageMain, IStorageMainOptions } from 'vs/platform/storage/electron-main/storageMain';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IS_NEW_KEY } from 'vs/platform/storage/common/storage';
|
||||
import { ILifecycleMainService, LifecycleMainPhase, ShutdownEvent, UnloadReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
|
||||
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
|
||||
suite('StorageMainService', function () {
|
||||
|
||||
class TestStorageMainService extends StorageMainService {
|
||||
|
||||
protected getStorageOptions(): IStorageMainOptions {
|
||||
return {
|
||||
useInMemoryStorage: true
|
||||
};
|
||||
}
|
||||
|
||||
protected enableMainWorkspaceStorage(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class StorageTestLifecycleMainService implements ILifecycleMainService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
onBeforeShutdown = Event.None;
|
||||
|
||||
private readonly _onWillShutdown = new Emitter<ShutdownEvent>();
|
||||
readonly onWillShutdown = this._onWillShutdown.event;
|
||||
|
||||
async fireOnWillShutdown(): Promise<void> {
|
||||
const joiners: Promise<void>[] = [];
|
||||
|
||||
this._onWillShutdown.fire({
|
||||
join(promise) {
|
||||
if (promise) {
|
||||
joiners.push(promise);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promises.settled(joiners);
|
||||
}
|
||||
|
||||
onWillLoadWindow = Event.None;
|
||||
onBeforeCloseWindow = Event.None;
|
||||
onBeforeUnloadWindow = Event.None;
|
||||
|
||||
wasRestarted = false;
|
||||
quitRequested = false;
|
||||
|
||||
phase = LifecycleMainPhase.Ready;
|
||||
|
||||
registerWindow(window: ICodeWindow): void { }
|
||||
async reload(window: ICodeWindow, cli?: NativeParsedArgs): Promise<void> { }
|
||||
async unload(window: ICodeWindow, reason: UnloadReason): Promise<boolean> { return true; }
|
||||
relaunch(options?: { addArgs?: string[] | undefined; removeArgs?: string[] | undefined; }): void { }
|
||||
async quit(fromUpdate?: boolean): Promise<boolean> { return true; }
|
||||
async kill(code?: number): Promise<void> { }
|
||||
async when(phase: LifecycleMainPhase): Promise<void> { }
|
||||
}
|
||||
|
||||
async function testStorage(storage: IStorageMain, isGlobal: boolean): Promise<void> {
|
||||
|
||||
// Telemetry: added after init
|
||||
if (isGlobal) {
|
||||
strictEqual(storage.items.size, 0);
|
||||
strictEqual(storage.get(instanceStorageKey), undefined);
|
||||
await storage.init();
|
||||
strictEqual(typeof storage.get(instanceStorageKey), 'string');
|
||||
strictEqual(typeof storage.get(firstSessionDateStorageKey), 'string');
|
||||
strictEqual(typeof storage.get(currentSessionDateStorageKey), 'string');
|
||||
} else {
|
||||
await storage.init();
|
||||
}
|
||||
|
||||
let storageChangeEvent: IStorageChangeEvent | undefined = undefined;
|
||||
const storageChangeListener = storage.onDidChangeStorage(e => {
|
||||
storageChangeEvent = e;
|
||||
});
|
||||
|
||||
let storageDidClose = false;
|
||||
const storageCloseListener = storage.onDidCloseStorage(() => storageDidClose = true);
|
||||
|
||||
// Basic store/get/remove
|
||||
const size = storage.items.size;
|
||||
|
||||
storage.set('bar', 'foo');
|
||||
strictEqual(storageChangeEvent!.key, 'bar');
|
||||
storage.set('barNumber', 55);
|
||||
storage.set('barBoolean', true);
|
||||
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.get('barNumber'), '55');
|
||||
strictEqual(storage.get('barBoolean'), 'true');
|
||||
|
||||
strictEqual(storage.items.size, size + 3);
|
||||
|
||||
storage.delete('bar');
|
||||
strictEqual(storage.get('bar'), undefined);
|
||||
|
||||
strictEqual(storage.items.size, size + 2);
|
||||
|
||||
// IS_NEW
|
||||
strictEqual(storage.get(IS_NEW_KEY), 'true');
|
||||
|
||||
// Close
|
||||
await storage.close();
|
||||
|
||||
strictEqual(storageDidClose, true);
|
||||
|
||||
storageChangeListener.dispose();
|
||||
storageCloseListener.dispose();
|
||||
}
|
||||
|
||||
test('basics (global)', function () {
|
||||
const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService());
|
||||
|
||||
return testStorage(storageMainService.globalStorage, true);
|
||||
});
|
||||
|
||||
test('basics (workspace)', function () {
|
||||
const workspace = { id: generateUuid() };
|
||||
const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService());
|
||||
|
||||
return testStorage(storageMainService.workspaceStorage(workspace), false);
|
||||
});
|
||||
|
||||
test('storage closed onWillShutdown', async function () {
|
||||
const lifecycleMainService = new StorageTestLifecycleMainService();
|
||||
const workspace = { id: generateUuid() };
|
||||
const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), lifecycleMainService, new TestConfigurationService());
|
||||
|
||||
let workspaceStorage = storageMainService.workspaceStorage(workspace);
|
||||
let didCloseWorkspaceStorage = false;
|
||||
workspaceStorage.onDidCloseStorage(() => {
|
||||
didCloseWorkspaceStorage = true;
|
||||
});
|
||||
|
||||
let globalStorage = storageMainService.globalStorage;
|
||||
let didCloseGlobalStorage = false;
|
||||
globalStorage.onDidCloseStorage(() => {
|
||||
didCloseGlobalStorage = true;
|
||||
});
|
||||
|
||||
strictEqual(workspaceStorage, storageMainService.workspaceStorage(workspace)); // same instance as long as not closed
|
||||
|
||||
await globalStorage.init();
|
||||
await workspaceStorage.init();
|
||||
|
||||
await lifecycleMainService.fireOnWillShutdown();
|
||||
|
||||
strictEqual(didCloseGlobalStorage, true);
|
||||
strictEqual(didCloseWorkspaceStorage, true);
|
||||
|
||||
let storage2 = storageMainService.workspaceStorage(workspace);
|
||||
notStrictEqual(workspaceStorage, storage2);
|
||||
|
||||
return storage2.close();
|
||||
});
|
||||
|
||||
test('storage closed before init works', async function () {
|
||||
const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService());
|
||||
const workspace = { id: generateUuid() };
|
||||
|
||||
let workspaceStorage = storageMainService.workspaceStorage(workspace);
|
||||
let didCloseWorkspaceStorage = false;
|
||||
workspaceStorage.onDidCloseStorage(() => {
|
||||
didCloseWorkspaceStorage = true;
|
||||
});
|
||||
|
||||
let globalStorage = storageMainService.globalStorage;
|
||||
let didCloseGlobalStorage = false;
|
||||
globalStorage.onDidCloseStorage(() => {
|
||||
didCloseGlobalStorage = true;
|
||||
});
|
||||
|
||||
await globalStorage.close();
|
||||
await workspaceStorage.close();
|
||||
|
||||
strictEqual(didCloseGlobalStorage, true);
|
||||
strictEqual(didCloseWorkspaceStorage, true);
|
||||
});
|
||||
|
||||
test('storage closed before init awaits works', async function () {
|
||||
const storageMainService = new TestStorageMainService(new NullLogService(), new NativeEnvironmentService(parseArgs(process.argv, OPTIONS)), new StorageTestLifecycleMainService(), new TestConfigurationService());
|
||||
const workspace = { id: generateUuid() };
|
||||
|
||||
let workspaceStorage = storageMainService.workspaceStorage(workspace);
|
||||
let didCloseWorkspaceStorage = false;
|
||||
workspaceStorage.onDidCloseStorage(() => {
|
||||
didCloseWorkspaceStorage = true;
|
||||
});
|
||||
|
||||
let globalStorage = storageMainService.globalStorage;
|
||||
let didCloseGlobalStorage = false;
|
||||
globalStorage.onDidCloseStorage(() => {
|
||||
didCloseGlobalStorage = true;
|
||||
});
|
||||
|
||||
globalStorage.init();
|
||||
workspaceStorage.init();
|
||||
|
||||
await globalStorage.close();
|
||||
await workspaceStorage.close();
|
||||
|
||||
strictEqual(didCloseGlobalStorage, true);
|
||||
strictEqual(didCloseWorkspaceStorage, true);
|
||||
});
|
||||
});
|
||||
@@ -7,47 +7,56 @@ import { strictEqual } from 'assert';
|
||||
import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
|
||||
import { tmpdir } from 'os';
|
||||
import { mkdirp, rimraf } from 'vs/base/node/pfs';
|
||||
import { promises } from 'fs';
|
||||
import { rimraf } from 'vs/base/node/pfs';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { createSuite } from 'vs/platform/storage/test/common/storageService.test';
|
||||
|
||||
flakySuite('NativeStorageService', function () {
|
||||
flakySuite('StorageService (native)', function () {
|
||||
|
||||
class StorageTestEnvironmentService extends NativeEnvironmentService {
|
||||
|
||||
constructor(private workspaceStorageFolderPath: URI, private _extensionsPath: string) {
|
||||
super(parseArgs(process.argv, OPTIONS));
|
||||
}
|
||||
|
||||
get workspaceStorageHome(): URI {
|
||||
return this.workspaceStorageFolderPath;
|
||||
}
|
||||
|
||||
get extensionsPath(): string {
|
||||
return this._extensionsPath;
|
||||
}
|
||||
}
|
||||
|
||||
let testDir: string;
|
||||
|
||||
setup(() => {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice');
|
||||
createSuite<NativeStorageService>({
|
||||
setup: async () => {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storageservice');
|
||||
|
||||
return mkdirp(testDir);
|
||||
});
|
||||
await promises.mkdir(testDir, { recursive: true });
|
||||
|
||||
teardown(() => {
|
||||
return rimraf(testDir);
|
||||
const storageService = new NativeStorageService(new InMemoryStorageDatabase(), { id: String(Date.now()) }, new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir));
|
||||
await storageService.initialize();
|
||||
|
||||
return storageService;
|
||||
},
|
||||
teardown: async storageService => {
|
||||
await storageService.close();
|
||||
|
||||
return rimraf(testDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('Migrate Data', async function () {
|
||||
|
||||
class StorageTestEnvironmentService extends NativeEnvironmentService {
|
||||
|
||||
constructor(private workspaceStorageFolderPath: URI, private _extensionsPath: string) {
|
||||
super(parseArgs(process.argv, OPTIONS));
|
||||
}
|
||||
|
||||
get workspaceStorageHome(): URI {
|
||||
return this.workspaceStorageFolderPath;
|
||||
}
|
||||
|
||||
get extensionsPath(): string {
|
||||
return this._extensionsPath;
|
||||
}
|
||||
}
|
||||
|
||||
const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir));
|
||||
await storage.initialize({ id: String(Date.now()) });
|
||||
const storage = new NativeStorageService(new InMemoryStorageDatabase(), { id: String(Date.now()) }, new NullLogService(), new StorageTestEnvironmentService(URI.file(testDir), testDir));
|
||||
await storage.initialize();
|
||||
|
||||
storage.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
storage.store('barNumber', 55, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
|
||||
Reference in New Issue
Block a user