storage - change main storage service to support global and workspace storage (stubs)

This commit is contained in:
Benjamin Pasero
2021-02-14 12:01:50 +01:00
parent 05922f8a26
commit 1f8bb6d33b
11 changed files with 753 additions and 300 deletions
@@ -58,8 +58,8 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService';
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { NativeStorageService2 } from 'vs/platform/storage/node/storageService2';
import { StorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UserDataSyncResourceEnablementService } from 'vs/platform/userDataSync/common/userDataSyncResourceEnablementService';
@@ -173,7 +173,8 @@ class SharedProcessMain extends Disposable {
await configurationService.initialize();
// Storage
const storageService = new NativeStorageService(new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')), logService, environmentService);
const storageDatabase = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), undefined /* no workspace access for shared process */);
const storageService = new NativeStorageService2(storageDatabase.globalStorage, storageDatabase.workspaceStorage, environmentService);
services.set(IStorageService, storageService);
await storageService.initialize();
+3 -3
View File
@@ -59,7 +59,7 @@ import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap';
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { StorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
@@ -582,7 +582,7 @@ export class CodeApplication extends Disposable {
// Storage
const storageMainService = new StorageMainService(this.logService, this.environmentService);
services.set(IStorageMainService, storageMainService);
this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close()));
this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.globalStorage.close()));
// Backups
const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService);
@@ -666,7 +666,7 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel('webview', webviewChannel);
// Storage (main & shared process)
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, accessor.get(IStorageMainService)));
const storageChannel = this._register(new StorageDatabaseChannel(this.logService, accessor.get(IStorageMainService)));
mainProcessElectronServer.registerChannel('storage', storageChannel);
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
+116 -88
View File
@@ -3,20 +3,24 @@
* 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, dispose, IDisposable } from 'vs/base/common/lifecycle';
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 { IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage';
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';
import { IStorageChangeEvent, IStorageMain } from 'vs/platform/storage/node/storageMain';
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
type Key = string;
type Value = string;
type Item = [Key, Value];
interface ISerializableUpdateRequest {
interface IWorkspaceArgument {
workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined
}
interface ISerializableUpdateRequest extends IWorkspaceArgument {
insert?: Item[];
delete?: Key[];
}
@@ -26,60 +30,34 @@ interface ISerializableItemsChangeEvent {
readonly deleted?: Key[];
}
export class GlobalStorageDatabaseChannel extends Disposable implements IServerChannel {
//#region --- Storage Server
export class StorageDatabaseChannel 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();
private readonly _onDidChangeGlobalStorage = this._register(new Emitter<ISerializableItemsChangeEvent>());
private readonly onDidChangeGlobalStorage = this._onDidChangeGlobalStorage.event;
constructor(
private logService: ILogService,
private storageMainService: IStorageMainService
) {
super();
// Trigger init of global storage directly from ctor
this.withStorageInitialized(undefined);
this.registerGlobalStorageListeners();
}
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}`);
}
//#region Global Storage Change Events
// 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 {
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.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => {
this._register(Event.debounce(this.storageMainService.globalStorage.onDidChangeStorage, (prev: IStorageChangeEvent[] | undefined, cur: IStorageChangeEvent) => {
if (!prev) {
prev = [cur];
} else {
@@ -87,18 +65,18 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
}
return prev;
}, GlobalStorageDatabaseChannel.STORAGE_CHANGE_DEBOUNCE_TIME)(events => {
}, StorageDatabaseChannel.STORAGE_CHANGE_DEBOUNCE_TIME)(events => {
if (events.length) {
this._onDidChangeItems.fire(this.serializeEvents(events));
this._onDidChangeGlobalStorage.fire(this.serializeGlobalStorageEvents(events));
}
}));
}
private serializeEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
private serializeGlobalStorageEvents(events: IStorageChangeEvent[]): ISerializableItemsChangeEvent {
const changed = new Map<Key, Value>();
const deleted = new Set<Key>();
events.forEach(event => {
const existing = this.storageMainService.get(event.key);
const existing = this.storageMainService.globalStorage.get(event.key);
if (typeof existing === 'string') {
changed.set(event.key, existing);
} else {
@@ -114,33 +92,35 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeItems': return this.onDidChangeItems;
case 'onDidChangeGlobalStorage': return this.onDidChangeGlobalStorage;
}
throw new Error(`Event not found: ${event}`);
}
async call(_: unknown, command: string, arg?: any): Promise<any> {
//#endregion
// ensure to always wait for ready
await this.whenReady;
async call(_: unknown, command: string, arg: IWorkspaceArgument): Promise<any> {
// Get storage to be ready
const storage = await this.withStorageInitialized(arg.workspace);
// handle call
switch (command) {
case 'getItems': {
return Array.from(this.storageMainService.items.entries());
return Array.from(storage.items.entries());
}
case 'updateItems': {
const items: ISerializableUpdateRequest = arg;
if (items.insert) {
for (const [key, value] of items.insert) {
this.storageMainService.store(key, value);
storage.store(key, value);
}
}
if (items.delete) {
items.delete.forEach(key => this.storageMainService.remove(key));
items.delete.forEach(key => storage.remove(key));
}
break;
@@ -150,44 +130,41 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
throw new Error(`Call not found: ${command}`);
}
}
private async withStorageInitialized(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined): Promise<IStorageMain> {
const storage = workspace ? this.storageMainService.workspaceStorage(workspace) : this.storageMainService.globalStorage;
try {
await storage.initialize();
} catch (error) {
this.logService.error(`[storage] init(): Unable to init ${workspace ? 'workspace' : 'global'} storage due to ${error}`);
}
return storage;
}
}
export class GlobalStorageDatabaseChannelClient extends Disposable implements IStorageDatabase {
//#endregion
declare readonly _serviceBrand: undefined;
//#region --- Storage Client
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
abstract class BaseStorageDatabase extends Disposable implements IStorageDatabase {
private onDidChangeItemsOnMainListener: IDisposable | undefined;
abstract onDidChangeItemsExternal: Event<IStorageItemsChangeEvent>;
constructor(private channel: IChannel) {
constructor(protected channel: IChannel, private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined) {
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');
const serializableRequest: IWorkspaceArgument = { workspace: this.workspace };
const items: Item[] = await this.channel.call('getItems', serializableRequest);
return new Map(items);
}
updateItems(request: IUpdateRequest): Promise<void> {
const serializableRequest: ISerializableUpdateRequest = Object.create(null);
const serializableRequest: ISerializableUpdateRequest = { workspace: this.workspace };
if (request.insert) {
serializableRequest.insert = Array.from(request.insert.entries());
@@ -200,15 +177,66 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS
return this.channel.call('updateItems', serializableRequest);
}
abstract close(recovery?: () => Map<string, string>): Promise<void>;
}
class GlobalStorageDatabase extends BaseStorageDatabase implements IStorageDatabase {
private readonly _onDidChangeItemsExternal = this._register(new Emitter<IStorageItemsChangeEvent>());
readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
private onDidChangeGlobalStorageListener: IDisposable | undefined;
constructor(channel: IChannel) {
super(channel, undefined);
this.registerListeners();
}
private registerListeners(): void {
this.onDidChangeGlobalStorageListener = 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> {
// 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);
// when we are about to close, we start to ignore global storage changes since we close anyway
dispose(this.onDidChangeGlobalStorageListener);
}
}
class WorkspaceStorageDatabase extends BaseStorageDatabase implements IStorageDatabase {
readonly onDidChangeItemsExternal = Event.None; // unsupported for workspace storage because we only ever write from one window
constructor(channel: IChannel, workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier) {
super(channel, workspace);
}
async close(): Promise<void> {
// TODO@bpasero close workspace storage?
}
}
export class StorageDatabaseChannelClient extends Disposable {
readonly globalStorage = new GlobalStorageDatabase(this.channel);
readonly workspaceStorage = this.workspace ? new WorkspaceStorageDatabase(this.channel, this.workspace) : undefined;
constructor(
private channel: IChannel,
private workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined
) {
super();
}
}
//#endregion
+370
View File
@@ -0,0 +1,370 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';
import { currentSessionDateStorageKey, firstSessionDateStorageKey, instanceStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { generateUuid } from 'vs/base/common/uuid';
/**
* Provides access to global and workspace storage from the
* electron-main side that is the owner of all storage connections.
*/
export interface IStorageMain {
/**
* 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;
/**
* Close the storage connection.
*/
close(): Promise<void>;
}
export interface IStorageChangeEvent {
key: string;
}
export class GlobalStorageMain extends Disposable implements IStorageMain {
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, GlobalStorageMain.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);
}
// Apply global telemetry values as part of the initialization
this.storeTelemetryStateOnce();
}
private storeTelemetryStateOnce(): void {
const instanceId = this.get(instanceStorageKey, undefined);
if (instanceId === undefined) {
this.store(instanceStorageKey, generateUuid());
}
const firstSessionDate = this.get(firstSessionDateStorageKey, undefined);
if (firstSessionDate === undefined) {
this.store(firstSessionDateStorageKey, new Date().toUTCString());
}
const lastSessionDate = this.get(currentSessionDateStorageKey, undefined); // previous session date was the "current" one at that time
const currentSessionDate = new Date().toUTCString(); // current session date is "now"
this.store(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate);
this.store(currentSessionDateStorageKey, currentSessionDate);
}
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();
}
}
export class WorkspaceStorageMain extends Disposable implements IStorageMain {
readonly onDidChangeStorage = Event.None;
readonly onWillSaveState = Event.None;
get items(): Map<string, string> { return this.storage.items; }
private storage: IStorage;
private initializePromise: Promise<void> | undefined;
constructor(
) {
super();
// Until the storage has been initialized, it can only be in memory
this.storage = new Storage(new InMemoryStorageDatabase());
}
async initialize(): Promise<void> {
if (!this.initializePromise) {
this.initializePromise = this.doInitialize();
}
return this.initializePromise;
}
private async doInitialize(): Promise<void> {
// private async initializeWorkspaceStorage(payload: IWorkspaceInitializationPayload): Promise<void> {
// // Prepare workspace storage folder for DB
// try {
// const result = await this.prepareWorkspaceStorageFolder(payload);
// const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests!
// // Create workspace storage and initialize
// mark('code/willInitWorkspaceStorage');
// try {
// const workspaceStorage = this.createWorkspaceStorage(
// useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME),
// result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined
// );
// await workspaceStorage.init();
// // Check to see if this is the first time we are "opening" this workspace
// const firstWorkspaceOpen = workspaceStorage.getBoolean(IS_NEW_KEY);
// if (firstWorkspaceOpen === undefined) {
// workspaceStorage.set(IS_NEW_KEY, result.wasCreated);
// } else if (firstWorkspaceOpen) {
// workspaceStorage.set(IS_NEW_KEY, false);
// }
// } finally {
// mark('code/didInitWorkspaceStorage');
// }
// } catch (error) {
// this.logService.error(`[storage] initializeWorkspaceStorage(): Unable to init workspace storage due to ${error}`);
// }
// }
// private createWorkspaceStorage(workspaceStoragePath: string, hint?: StorageHint): IStorage {
// // Logger for workspace storage
// const workspaceLoggingOptions: ISQLiteStorageDatabaseLoggingOptions = {
// logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined,
// logError: error => this.logService.error(error)
// };
// // Dispose old (if any)
// dispose(this.workspaceStorage);
// dispose(this.workspaceStorageListener);
// // Create new
// this.workspaceStoragePath = workspaceStoragePath;
// this.workspaceStorage = new Storage(new SQLiteStorageDatabase(workspaceStoragePath, { logging: workspaceLoggingOptions }), { hint });
// this.workspaceStorageListener = this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key));
// return this.workspaceStorage;
// }
// private getWorkspaceStorageFolderPath(payload: IWorkspaceInitializationPayload): string {
// return join(this.environmentService.workspaceStorageHome.fsPath, payload.id); // workspace home + workspace id;
// }
// private async prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Promise<{ path: string, wasCreated: boolean }> {
// const workspaceStorageFolderPath = this.getWorkspaceStorageFolderPath(payload);
// const storageExists = await exists(workspaceStorageFolderPath);
// if (storageExists) {
// return { path: workspaceStorageFolderPath, wasCreated: false };
// }
// await promises.mkdir(workspaceStorageFolderPath, { recursive: true });
// // Write metadata into folder
// this.ensureWorkspaceStorageFolderMeta(payload);
// return { path: workspaceStorageFolderPath, wasCreated: true };
// }
// private ensureWorkspaceStorageFolderMeta(payload: IWorkspaceInitializationPayload): void {
// let meta: object | undefined = undefined;
// if (isSingleFolderWorkspaceIdentifier(payload)) {
// meta = { folder: payload.uri.toString() };
// } else if (isWorkspaceIdentifier(payload)) {
// meta = { workspace: payload.configPath.toString() };
// }
// if (meta) {
// (async () => {
// try {
// const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME);
// const storageExists = await exists(workspaceStorageMetaPath);
// if (!storageExists) {
// await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2));
// }
// } catch (error) {
// this.logService.error(error);
// }
// })();
// }
// }
}
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> {
return this.storage.close();
}
}
@@ -3,15 +3,11 @@
* 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';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { GlobalStorageMain, IStorageMain, WorkspaceStorageMain } from 'vs/platform/storage/node/storageMain';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export const IStorageMainService = createDecorator<IStorageMainService>('storageMainService');
@@ -20,172 +16,50 @@ export interface IStorageMainService {
readonly _serviceBrand: undefined;
/**
* Emitted whenever data is updated or deleted.
* Provides access to the global storage shared across all windows.
*/
readonly onDidChangeStorage: Event<IStorageChangeEvent>;
readonly globalStorage: IStorageMain;
/**
* 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.
* Provides access to the workspace storage specific to a single window.
*/
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;
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain;
}
export interface IStorageChangeEvent {
key: string;
}
export class StorageMainService extends Disposable implements IStorageMainService {
export class StorageMainService implements IStorageMainService {
declare readonly _serviceBrand: undefined;
private static readonly STORAGE_NAME = 'state.vscdb';
readonly globalStorage = this.createGlobalStorage();
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;
private readonly mapWorkspaceToStorage = new Map<string, IStorageMain>();
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!
private createGlobalStorage(): IStorageMain {
const globalStorage = new GlobalStorageMain(this.logService, this.environmentService);
return globalStorage;
}
private createWorkspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain {
const workspaceStorage = new WorkspaceStorageMain();
// TODO@bpasero lifecycle like global storage? window events? crashes?
return workspaceStorage;
}
workspaceStorage(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): IStorageMain {
let workspaceStorage = this.mapWorkspaceToStorage.get(workspace.id);
if (!workspaceStorage) {
workspaceStorage = this.createWorkspaceStorage(workspace);
this.mapWorkspaceToStorage.set(workspace.id, workspaceStorage);
}
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();
return workspaceStorage;
}
}
@@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { StorageScope, WillSaveStateReason, logStorage, AbstractStorageService } from 'vs/platform/storage/common/storage';
import { Storage, IStorageDatabase, IStorage } from 'vs/base/parts/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces';
import { assertIsDefined } from 'vs/base/common/types';
import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
export class NativeStorageService2 extends AbstractStorageService {
private readonly globalStorage = new Storage(this.globalStorageDatabase);
private readonly workspaceStorage = this.workspaceStorageDatabase ? new Storage(this.workspaceStorageDatabase) : 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 workspaceStorageDatabase: IStorageDatabase | undefined,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key)));
this._register(this.workspaceStorage?.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key)) ?? Disposable.None);
}
initialize(): Promise<void> {
if (!this.initializePromise) {
this.initializePromise = this.doInitialize();
}
return this.initializePromise;
}
private async doInitialize(): Promise<void> {
// Init all storage locations
await Promises.settled([
this.globalStorage.init(),
this.workspaceStorage?.init() ?? 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();
}
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);
}
protected doStore(key: string, value: string | boolean | number | undefined | null, scope: StorageScope): void {
this.getStorage(scope).set(key, value);
}
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);
}
protected async doFlush(): Promise<void> {
await Promises.settled([
this.globalStorage.whenFlushed(),
this.workspaceStorage?.whenFlushed() ?? Promise.resolve()
]);
}
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();
});
}
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;
// 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 logStorage(): Promise<void> {
return logStorage(
this.globalStorage.items,
this.workspaceStorage ? this.workspaceStorage.items : new Map<string, string>(),
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();
// // Prepare new workspace storage folder
// const result = await this.prepareWorkspaceStorageFolder(toWorkspace);
// const newWorkspaceStoragePath = join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME);
// // Copy current storage over to new workspace storage
// await copy(assertIsDefined(this.workspaceStoragePath), newWorkspaceStoragePath, { preserveSymlinks: false });
// // Recreate and init workspace storage
// return this.createWorkspaceStorage(newWorkspaceStoragePath).init();
}
}
@@ -114,6 +114,7 @@ export interface IWindowSettings {
readonly enableMenuBarMnemonics: boolean;
readonly closeWhenEmpty: boolean;
readonly clickThroughInactive: boolean;
readonly enableExperimentalMainProcessWorkspaceStorage: boolean;
}
export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' {
@@ -253,6 +254,8 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
filesToWait?: IPathsToWaitFor;
os: IOSConfiguration;
enableExperimentalMainProcessWorkspaceStorage: boolean;
}
/**
+48 -43
View File
@@ -114,9 +114,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
constructor(
config: IWindowCreationOptions,
@ILogService private readonly logService: ILogService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@IFileService private readonly fileService: IFileService,
@IStorageMainService storageService: IStorageMainService,
@IStorageMainService storageMainService: IStorageMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeMainService private readonly themeMainService: IThemeMainService,
@IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService,
@@ -158,7 +158,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
nativeWindowOpen: true,
webviewTag: true,
zoomFactor: zoomLevelToZoomFactor(windowConfig?.zoomLevel),
...this.environmentService.sandbox ?
...this.environmentMainService.sandbox ?
// Sandbox
{
@@ -181,9 +181,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Linux: always
// Windows: only when running out of sources, otherwise an icon is set by us on the executable
if (isLinux) {
options.icon = join(this.environmentService.appRoot, 'resources/linux/code.png');
} else if (isWindows && !this.environmentService.isBuilt) {
options.icon = join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
options.icon = join(this.environmentMainService.appRoot, 'resources/linux/code.png');
} else if (isWindows && !this.environmentMainService.isBuilt) {
options.icon = join(this.environmentMainService.appRoot, 'resources/win32/code_150x150.png');
}
if (isMacintosh && !this.useNativeFullScreen()) {
@@ -217,7 +217,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._id = this._win.id;
// Open devtools if instructed from command line args
if (this.environmentService.args['open-devtools'] === true) {
if (this.environmentMainService.args['open-devtools'] === true) {
this._win.webContents.openDevTools();
}
@@ -267,9 +267,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.createTouchBar();
// Request handling
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, {
get(key) { return storageService.get(key); },
store(key, value) { storageService.store(key, value); }
this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentMainService, this.fileService, {
get: key => storageMainService.globalStorage.get(key),
store: (key, value) => storageMainService.globalStorage.store(key, value)
});
// Eventing
@@ -535,20 +535,23 @@ export class CodeWindow extends Disposable implements ICodeWindow {
});
// Handle configuration changes
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
this._register(this.configurationService.onDidChangeConfiguration(() => this.onConfigurationUpdated()));
// Handle Workspace events
this._register(this.workspacesManagementMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
// Inject headers when requests are incoming
const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) =>
this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) })));
this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, async (details, cb) => {
const headers = await this.marketplaceHeadersPromise;
cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) });
});
}
private onWindowError(error: WindowError.UNRESPONSIVE): void;
private onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): void;
private onWindowError(error: WindowError, details?: RenderProcessGoneDetails): void {
private async onWindowError(error: WindowError.UNRESPONSIVE): Promise<void>;
private async onWindowError(error: WindowError.CRASHED, details: RenderProcessGoneDetails): Promise<void>;
private async onWindowError(error: WindowError, details?: RenderProcessGoneDetails): Promise<void> {
this.logService.error(error === WindowError.CRASHED ? `Main: renderer process crashed (detail: ${details?.reason})` : 'Main: detected unresponsive');
// If we run extension tests from CLI, showing a dialog is not
@@ -581,25 +584,25 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
// Show Dialog
this.dialogMainService.showMessageBox({
const result = await this.dialogMainService.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message: localize('appStalled', "The window is no longer responding"),
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
noLink: true
}, this._win).then(result => {
if (!this._win) {
return; // Return early if the window has been going down already
}
}, this._win);
if (result.response === 0) {
this._win.webContents.forcefullyCrashRenderer(); // Calling reload() immediately after calling this method will force the reload to occur in a new process
this.reload();
} else if (result.response === 2) {
this.destroyWindow();
}
});
if (!this._win) {
return; // Return early if the window has been going down already
}
if (result.response === 0) {
this._win.webContents.forcefullyCrashRenderer(); // Calling reload() immediately after calling this method will force the reload to occur in a new process
this.reload();
} else if (result.response === 2) {
this.destroyWindow();
}
}
// Crashed
@@ -611,24 +614,24 @@ export class CodeWindow extends Disposable implements ICodeWindow {
message = localize('appCrashed', "The window has crashed", details?.reason);
}
this.dialogMainService.showMessageBox({
const result = await this.dialogMainService.showMessageBox({
title: product.nameLong,
type: 'warning',
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
message,
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
noLink: true
}, this._win).then(result => {
if (!this._win) {
return; // Return early if the window has been going down already
}
}, this._win);
if (result.response === 0) {
this.reload();
} else if (result.response === 1) {
this.destroyWindow();
}
});
if (!this._win) {
return; // Return early if the window has been going down already
}
if (result.response === 0) {
this.reload();
} else if (result.response === 1) {
this.destroyWindow();
}
}
}
@@ -747,7 +750,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Make window visible if it did not open in N seconds because this indicates an error
// Only do this when running out of sources and not when running tests
if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsLocationURI) {
if (!this.environmentMainService.isBuilt && !this.environmentMainService.extensionTestsLocationURI) {
this.showTimeoutHandle = setTimeout(() => {
if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
this._win.show();
@@ -824,7 +827,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
windowConfiguration.windowId = this._win.id;
windowConfiguration.sessionId = `window:${this._win.id}`;
windowConfiguration.logLevel = this.logService.getLevel();
windowConfiguration.logsPath = this.environmentService.logsPath;
windowConfiguration.logsPath = this.environmentMainService.logsPath;
// Set zoomlevel
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
@@ -851,13 +854,15 @@ export class CodeWindow extends Disposable implements ICodeWindow {
windowConfiguration.perfMarks = getMarks();
// Parts splash
windowConfiguration.partsSplashPath = join(this.environmentService.userDataPath, 'rapid_render.json');
windowConfiguration.partsSplashPath = join(this.environmentMainService.userDataPath, 'rapid_render.json');
// OS Info
windowConfiguration.os = {
release: release()
};
windowConfiguration.enableExperimentalMainProcessWorkspaceStorage = !!(windowConfig?.enableExperimentalMainProcessWorkspaceStorage);
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv, OPTIONS);
const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown };
@@ -887,7 +892,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private doGetUrl(config: object): string {
let workbench: string;
if (this.environmentService.sandbox) {
if (this.environmentMainService.sandbox) {
workbench = 'vs/code/electron-sandbox/workbench/workbench.html';
} else {
workbench = 'vs/code/electron-browser/workbench/workbench.html';
@@ -18,11 +18,12 @@ import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environ
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { NativeStorageService2 } from 'vs/platform/storage/node/storageService2';
import { Schemas } from 'vs/base/common/network';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { StorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -130,14 +131,14 @@ class DesktopMain extends Disposable {
services.logService.trace('workbench configuration', JSON.stringify(this.configuration));
}
private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
private registerListeners(workbench: Workbench, storageService: NativeStorageService | NativeStorageService2): void {
// Workbench Lifecycle
this._register(workbench.onShutdown(() => this.dispose()));
this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage')));
}
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService | NativeStorageService2 }> {
const serviceCollection = new ServiceCollection();
@@ -319,9 +320,15 @@ class DesktopMain extends Disposable {
}
}
private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage'));
const storageService = new NativeStorageService(globalStorageDatabase, logService, this.environmentService);
private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise<NativeStorageService | NativeStorageService2> {
const storageDataBase = new StorageDatabaseChannelClient(mainProcessService.getChannel('storage'), isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : undefined);
let storageService: NativeStorageService | NativeStorageService2;
if (this.configuration.enableExperimentalMainProcessWorkspaceStorage) {
storageService = new NativeStorageService2(storageDataBase.globalStorage, storageDataBase.workspaceStorage, this.environmentService);
} else {
storageService = new NativeStorageService(storageDataBase.globalStorage, logService, this.environmentService);
}
try {
await storageService.initialize(payload);
@@ -273,6 +273,12 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
'scope': ConfigurationScope.APPLICATION,
'description': localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."),
'included': isMacintosh
},
'window.enableExperimentalMainProcessWorkspaceStorage': {
'type': 'boolean',
'default': false,
'scope': ConfigurationScope.APPLICATION,
'description': localize('window.localize', "Enables workspace storage access from the main process. Requires a restart to take effect."),
}
}
});
@@ -55,6 +55,7 @@ export const TestWorkbenchConfiguration: INativeWorkbenchConfiguration = {
perfMarks: [],
colorScheme: { dark: true, highContrast: false },
os: { release: release() },
enableExperimentalMainProcessWorkspaceStorage: false,
...parseArgs(process.argv, OPTIONS)
};