diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2cdc281d766..f6810b19115 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -65,7 +65,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess'; import { ISignService } from 'vs/platform/sign/common/sign'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc'; import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; @@ -131,7 +131,7 @@ export class CodeApplication extends Disposable { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, @@ -528,7 +528,7 @@ export class CodeApplication extends Disposable { // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); - const machineId = await resolveMachineId(this.stateMainService); + const machineId = await resolveMachineId(this.stateService); this.logService.trace(`Resolved machine identifier: ${machineId}`); // Shared process @@ -920,7 +920,7 @@ export class CodeApplication extends Disposable { } // Backups - const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateMainService); + const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateService); services.set(IBackupMainService, backupMainService); // Workspaces diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 0a26c5bc9d5..67affd19f69 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -56,8 +56,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; -import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { IStateReadService, IStateService } from 'vs/platform/state/node/state'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IUserDataProfilesMainService, UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; @@ -70,6 +69,7 @@ import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentitySe import { ILoggerMainService, LoggerMainService } from 'vs/platform/log/electron-main/loggerService'; import { LogService } from 'vs/platform/log/common/logService'; import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; /** * The main VS Code entry point. @@ -147,7 +147,7 @@ class CodeMain { } } - private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateMainService, BufferLogger, IProductService, UserDataProfilesMainService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogger, IProductService, UserDataProfilesMainService] { const services = new ServiceCollection(); const disposables = new DisposableStore(); process.once('exit', () => disposables.dispose()); @@ -183,11 +183,12 @@ class CodeMain { services.set(IUriIdentityService, uriIdentityService); // State - const stateMainService = new StateMainService(environmentMainService, logService, fileService); - services.set(IStateMainService, stateMainService); + const stateService = new StateService(SaveStrategy.DELAYED, environmentMainService, logService, fileService); + services.set(IStateReadService, stateService); + services.set(IStateService, stateService); // User Data Profiles - const userDataProfilesMainService = new UserDataProfilesMainService(stateMainService, uriIdentityService, environmentMainService, fileService, logService); + const userDataProfilesMainService = new UserDataProfilesMainService(stateService, uriIdentityService, environmentMainService, fileService, logService); services.set(IUserDataProfilesMainService, userDataProfilesMainService); // Policy @@ -218,7 +219,7 @@ class CodeMain { // Protocol (instantiated early and not using sync descriptor for security reasons) services.set(IProtocolMainService, new ProtocolMainService(environmentMainService, userDataProfilesMainService, logService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogger, productService, userDataProfilesMainService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogger, productService, userDataProfilesMainService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -238,7 +239,7 @@ class CodeMain { return instanceEnvironment; } - private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateMainService: StateMainService, productService: IProductService): Promise { + private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateService: StateService, productService: IProductService): Promise { await Promises.settled([ // Environment service (paths) @@ -253,7 +254,7 @@ class CodeMain { ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), // State service - stateMainService.init(), + stateService.init(), // Configuration service configurationService.initialize() diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 737dadadcaf..dff743e9eac 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -48,8 +48,7 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; -import { IStateService } from 'vs/platform/state/node/state'; -import { StateService } from 'vs/platform/state/node/stateService'; +import { SaveStrategy, StateReadonlyService } from 'vs/platform/state/node/stateService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; @@ -59,7 +58,7 @@ import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { UserDataProfilesReadonlyService } from 'vs/platform/userDataProfile/node/userDataProfile'; import { resolveMachineId } from 'vs/platform/telemetry/node/telemetryUtils'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; import { LogService } from 'vs/platform/log/common/logService'; @@ -144,15 +143,14 @@ class CliMain extends Disposable { fileService.registerProvider(Schemas.file, diskFileSystemProvider); // State - const stateService = new StateService(environmentService, logService, fileService); - services.set(IStateService, stateService); // Uri Identity const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); // User Data Profiles - const userDataProfilesService = new UserDataProfilesService(stateService, uriIdentityService, environmentService, fileService, logService); + const stateService = new StateReadonlyService(SaveStrategy.DELAYED, environmentService, logService, fileService); + const userDataProfilesService = new UserDataProfilesReadonlyService(stateService, uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); // Policy diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index e8201c2ca80..3d48cf54ad3 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -14,7 +14,7 @@ import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { ISerializedBackupWorkspaces, IEmptyWindowBackupInfo, isEmptyWindowBackupInfo, deserializeWorkspaceInfos, deserializeFolderInfos, ISerializedWorkspaceBackupInfo, ISerializedFolderBackupInfo, ISerializedEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { HotExitConfiguration, IFilesConfiguration } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IFolderBackupInfo, isFolderBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; @@ -43,14 +43,14 @@ export class BackupMainService implements IBackupMainService { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService, - @IStateMainService private readonly stateMainService: IStateMainService + @IStateService private readonly stateService: IStateService ) { } async initialize(): Promise { // read backup workspaces - const serializedBackupWorkspaces = this.stateMainService.getItem(BackupMainService.backupWorkspacesMetadataStorageKey) ?? { workspaces: [], folders: [], emptyWindows: [] }; + const serializedBackupWorkspaces = this.stateService.getItem(BackupMainService.backupWorkspacesMetadataStorageKey) ?? { workspaces: [], folders: [], emptyWindows: [] }; // validate empty workspaces backups first this.emptyWindows = await this.validateEmptyWorkspaces(serializedBackupWorkspaces.emptyWindows); @@ -397,7 +397,7 @@ export class BackupMainService implements IBackupMainService { }) }; - this.stateMainService.setItem(BackupMainService.backupWorkspacesMetadataStorageKey, serializedBackupWorkspaces); + this.stateService.setItem(BackupMainService.backupWorkspacesMetadataStorageKey, serializedBackupWorkspaces); } protected getFolderHash(folder: IFolderBackupInfo): string { diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 001f722278e..fa7935a1131 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -26,7 +26,7 @@ import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; import { IWindowState } from 'vs/platform/window/electron-main/window'; import { randomPath } from 'vs/base/common/extpath'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; export const IIssueMainService = createDecorator('issueMainService'); const processExplorerWindowState = 'issue.processExplorerWindowState'; @@ -66,7 +66,7 @@ export class IssueMainService implements IIssueMainService { @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IProductService private readonly productService: IProductService, - @IStateMainService private readonly stateMainService: IStateMainService + @IStateService private readonly stateService: IStateService ) { this.registerListeners(); } @@ -255,7 +255,7 @@ export class IssueMainService implements IIssueMainService { const processExplorerWindowConfigUrl = processExplorerDisposables.add(this.protocolMainService.createIPCObjectUrl()); - const savedPosition = this.stateMainService.getItem(processExplorerWindowState, undefined); + const savedPosition = this.stateService.getItem(processExplorerWindowState, undefined); const position = isStrictWindowState(savedPosition) ? savedPosition : this.getWindowPosition(this.processExplorerParentWindow, 800, 500); // Correct dimensions to take scale/dpr into account @@ -312,7 +312,7 @@ export class IssueMainService implements IIssueMainService { x: position[0], y: position[1] }; - this.stateMainService.setItem(processExplorerWindowState, state); + this.stateService.setItem(processExplorerWindowState, state); }; this.processExplorerWindow.on('moved', storeState); diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 2e5086567f1..db55fec521f 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -14,7 +14,7 @@ import { assertIsDefined } from 'vs/base/common/types'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { ICodeWindow, LoadReason, UnloadReason } from 'vs/platform/window/electron-main/window'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -226,7 +226,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe constructor( @ILogService private readonly logService: ILogService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService ) { super(); @@ -236,11 +236,11 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe } private resolveRestarted(): void { - this._wasRestarted = !!this.stateMainService.getItem(LifecycleMainService.QUIT_AND_RESTART_KEY); + this._wasRestarted = !!this.stateService.getItem(LifecycleMainService.QUIT_AND_RESTART_KEY); if (this._wasRestarted) { // remove the marker right after if found - this.stateMainService.removeItem(LifecycleMainService.QUIT_AND_RESTART_KEY); + this.stateService.removeItem(LifecycleMainService.QUIT_AND_RESTART_KEY); } } @@ -347,7 +347,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Then, always make sure at the end // the state service is flushed. try { - await this.stateMainService.close(); + await this.stateService.close(); } catch (error) { this.logService.error(error); } @@ -563,7 +563,7 @@ export class LifecycleMainService extends Disposable implements ILifecycleMainSe // Remember if we are about to restart if (willRestart) { - this.stateMainService.setItem(LifecycleMainService.QUIT_AND_RESTART_KEY, true); + this.stateService.setItem(LifecycleMainService.QUIT_AND_RESTART_KEY, true); } this.pendingQuitPromise = new Promise(resolve => { diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 7256fca4579..d530756c5b5 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -18,7 +18,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IMenubarData, IMenubarKeybinding, IMenubarMenu, IMenubarMenuRecentItemAction, isMenubarMenuItemAction, isMenubarMenuItemRecentAction, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; import { getTitleBarStyle, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/window/common/window'; @@ -70,7 +70,7 @@ export class Menubar { @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService, @@ -100,7 +100,7 @@ export class Menubar { } private restoreCachedMenubarData() { - const menubarData = this.stateMainService.getItem(Menubar.lastKnownMenubarStorageKey); + const menubarData = this.stateService.getItem(Menubar.lastKnownMenubarStorageKey); if (menubarData) { if (menubarData.menus) { this.menubarMenus = menubarData.menus; @@ -197,7 +197,7 @@ export class Menubar { this.keybindings = menubarData.keybindings; // Save off new menu and keybindings - this.stateMainService.setItem(Menubar.lastKnownMenubarStorageKey, menubarData); + this.stateService.setItem(Menubar.lastKnownMenubarStorageKey, menubarData); this.scheduleUpdateMenu(); } diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index f29f07c4e43..91e1efc080f 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -42,7 +42,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl'; import { WindowProfiler } from 'vs/platform/profiling/electron-main/windowProfiling'; import { IV8Profile } from 'vs/platform/profiling/common/profiling'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -61,7 +61,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IThemeMainService private readonly themeMainService: IThemeMainService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @IWorkspacesManagementMainService private readonly workspacesManagementMainService: IWorkspacesManagementMainService ) { super(); @@ -770,9 +770,9 @@ export class NativeHostMainService extends Disposable implements INativeHostMain async enableSandbox(windowId: number | undefined, enabled: boolean): Promise { if (enabled) { - this.stateMainService.setItem('window.experimental.useSandbox', true); + this.stateService.setItem('window.experimental.useSandbox', true); } else { - this.stateMainService.removeItem('window.experimental.useSandbox'); + this.stateService.removeItem('window.experimental.useSandbox'); } } diff --git a/src/vs/platform/state/electron-main/state.ts b/src/vs/platform/state/electron-main/state.ts deleted file mode 100644 index c215cde7ac6..00000000000 --- a/src/vs/platform/state/electron-main/state.ts +++ /dev/null @@ -1,21 +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 { IStateService } from 'vs/platform/state/node/state'; - -export const IStateMainService = createDecorator('stateMainService'); - -export interface IStateMainService extends IStateService { - - readonly _serviceBrand: undefined; - - setItem(key: string, data?: object | string | number | boolean | undefined | null): void; - setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void; - - removeItem(key: string): void; - - close(): Promise; -} diff --git a/src/vs/platform/state/electron-main/stateMainService.ts b/src/vs/platform/state/electron-main/stateMainService.ts deleted file mode 100644 index 3235a351ff4..00000000000 --- a/src/vs/platform/state/electron-main/stateMainService.ts +++ /dev/null @@ -1,28 +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 { IStateMainService } from 'vs/platform/state/electron-main/state'; -import { StateService } from 'vs/platform/state/node/stateService'; - -export class StateMainService extends StateService implements IStateMainService { - - declare readonly _serviceBrand: undefined; - - setItem(key: string, data?: object | string | number | boolean | undefined | null): void { - this.fileStorage.setItem(key, data); - } - - setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void { - this.fileStorage.setItems(items); - } - - removeItem(key: string): void { - this.fileStorage.removeItem(key); - } - - close(): Promise { - return this.fileStorage.close(); - } -} diff --git a/src/vs/platform/state/node/state.ts b/src/vs/platform/state/node/state.ts index a02b3990bf7..a125c9955b9 100644 --- a/src/vs/platform/state/node/state.ts +++ b/src/vs/platform/state/node/state.ts @@ -5,9 +5,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const IStateService = createDecorator('stateService'); - -export interface IStateService { +export const IStateReadService = createDecorator('stateReadService'); +export interface IStateReadService { readonly _serviceBrand: undefined; @@ -15,3 +14,16 @@ export interface IStateService { getItem(key: string, defaultValue?: T): T | undefined; } + +export const IStateService = createDecorator('stateService'); +export interface IStateService extends IStateReadService { + + readonly _serviceBrand: undefined; + + setItem(key: string, data?: object | string | number | boolean | undefined | null): void; + setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void; + + removeItem(key: string): void; + + close(): Promise; +} diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 97bd95d98f5..073ec83ef39 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -10,25 +10,32 @@ import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateService } from 'vs/platform/state/node/state'; +import { IStateReadService, IStateService } from 'vs/platform/state/node/state'; type StorageDatabase = { [key: string]: unknown }; +export const enum SaveStrategy { + IMMEDIATE, + DELAYED +} + export class FileStorage { private storage: StorageDatabase = Object.create(null); private lastSavedStorageContents = ''; - private readonly flushDelayer = new ThrottledDelayer(100 /* buffer saves over a short time */); + private readonly flushDelayer: ThrottledDelayer | undefined; private initializing: Promise | undefined = undefined; private closing: Promise | undefined = undefined; constructor( private readonly storagePath: URI, + saveStrategy: SaveStrategy, private readonly logService: ILogService, - private readonly fileService: IFileService + private readonly fileService: IFileService, ) { + this.flushDelayer = saveStrategy === SaveStrategy.IMMEDIATE ? undefined : new ThrottledDelayer(100 /* buffer saves over a short time */); } init(): Promise { @@ -109,7 +116,11 @@ export class FileStorage { return; // already about to close } - return this.flushDelayer.trigger(() => this.doSave()); + if (this.flushDelayer) { + return this.flushDelayer.trigger(() => this.doSave()); + } + + return this.doSave(); } private async doSave(): Promise { @@ -137,25 +148,28 @@ export class FileStorage { async close(): Promise { if (!this.closing) { - this.closing = this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */); + this.closing = this.flushDelayer + ? this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */) + : this.doSave(); } return this.closing; } } -export class StateService implements IStateService { +export class StateReadonlyService implements IStateReadService { declare readonly _serviceBrand: undefined; protected readonly fileStorage: FileStorage; constructor( + saveStrategy: SaveStrategy, @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService, @IFileService fileService: IFileService ) { - this.fileStorage = new FileStorage(environmentService.stateResource, logService, fileService); + this.fileStorage = new FileStorage(environmentService.stateResource, saveStrategy, logService, fileService); } async init(): Promise { @@ -168,3 +182,24 @@ export class StateService implements IStateService { return this.fileStorage.getItem(key, defaultValue); } } + +export class StateService extends StateReadonlyService implements IStateService { + + declare readonly _serviceBrand: undefined; + + setItem(key: string, data?: object | string | number | boolean | undefined | null): void { + this.fileStorage.setItem(key, data); + } + + setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void { + this.fileStorage.setItems(items); + } + + removeItem(key: string): void { + this.fileStorage.removeItem(key); + } + + close(): Promise { + return this.fileStorage.close(); + } +} diff --git a/src/vs/platform/state/test/electron-main/state.test.ts b/src/vs/platform/state/test/node/state.test.ts similarity index 59% rename from src/vs/platform/state/test/electron-main/state.test.ts rename to src/vs/platform/state/test/node/state.test.ts index a37f31b3100..af5d0781dd6 100644 --- a/src/vs/platform/state/test/electron-main/state.test.ts +++ b/src/vs/platform/state/test/node/state.test.ts @@ -15,9 +15,9 @@ import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { FileStorage } from 'vs/platform/state/node/stateService'; +import { FileStorage, SaveStrategy } from 'vs/platform/state/node/stateService'; -flakySuite('StateMainService', () => { +flakySuite('StateService', () => { let testDir: string; let fileService: IFileService; @@ -43,11 +43,11 @@ flakySuite('StateMainService', () => { return Promises.rm(testDir); }); - test('Basics', async function () { + test('Basics (delayed strategy)', async function () { const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); - let service = new FileStorage(URI.file(storageFile), logService, fileService); + let service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); await service.init(); service.setItem('some.key', 'some.value'); @@ -62,7 +62,69 @@ flakySuite('StateMainService', () => { await service.close(); - service = new FileStorage(URI.file(storageFile), logService, fileService); + service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); + await service.init(); + + assert.strictEqual(service.getItem('some.other.key'), 'some.other.value'); + + service.setItem('some.other.key', 'some.other.value'); + assert.strictEqual(service.getItem('some.other.key'), 'some.other.value'); + + service.setItem('some.undefined.key', undefined); + assert.strictEqual(service.getItem('some.undefined.key', 'some.default'), 'some.default'); + + service.setItem('some.null.key', null); + assert.strictEqual(service.getItem('some.null.key', 'some.default'), 'some.default'); + + service.setItems([ + { key: 'some.setItems.key1', data: 'some.value' }, + { key: 'some.setItems.key2', data: 0 }, + { key: 'some.setItems.key3', data: true }, + { key: 'some.setItems.key4', data: null }, + { key: 'some.setItems.key5', data: undefined } + ]); + + assert.strictEqual(service.getItem('some.setItems.key1'), 'some.value'); + assert.strictEqual(service.getItem('some.setItems.key2'), 0); + assert.strictEqual(service.getItem('some.setItems.key3'), true); + assert.strictEqual(service.getItem('some.setItems.key4'), undefined); + assert.strictEqual(service.getItem('some.setItems.key5'), undefined); + + service.setItems([ + { key: 'some.setItems.key1', data: undefined }, + { key: 'some.setItems.key2', data: undefined }, + { key: 'some.setItems.key3', data: undefined }, + { key: 'some.setItems.key4', data: null }, + { key: 'some.setItems.key5', data: undefined } + ]); + + assert.strictEqual(service.getItem('some.setItems.key1'), undefined); + assert.strictEqual(service.getItem('some.setItems.key2'), undefined); + assert.strictEqual(service.getItem('some.setItems.key3'), undefined); + assert.strictEqual(service.getItem('some.setItems.key4'), undefined); + assert.strictEqual(service.getItem('some.setItems.key5'), undefined); + }); + + test('Basics (immediate strategy)', async function () { + const storageFile = join(testDir, 'storage.json'); + writeFileSync(storageFile, ''); + + let service = new FileStorage(URI.file(storageFile), SaveStrategy.IMMEDIATE, logService, fileService); + await service.init(); + + service.setItem('some.key', 'some.value'); + assert.strictEqual(service.getItem('some.key'), 'some.value'); + + service.removeItem('some.key'); + assert.strictEqual(service.getItem('some.key', 'some.default'), 'some.default'); + + assert.ok(!service.getItem('some.unknonw.key')); + + service.setItem('some.other.key', 'some.other.value'); + + await service.close(); + + service = new FileStorage(URI.file(storageFile), SaveStrategy.IMMEDIATE, logService, fileService); await service.init(); assert.strictEqual(service.getItem('some.other.key'), 'some.other.value'); @@ -109,7 +171,7 @@ flakySuite('StateMainService', () => { const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); - let service = new FileStorage(URI.file(storageFile), logService, fileService); + let service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); await service.init(); service.setItem('some.key1', 'some.value1'); @@ -125,7 +187,36 @@ flakySuite('StateMainService', () => { await service.close(); - service = new FileStorage(URI.file(storageFile), logService, fileService); + service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); + await service.init(); + + assert.strictEqual(service.getItem('some.key1'), 'some.value1'); + assert.strictEqual(service.getItem('some.key2'), 'some.value2'); + assert.strictEqual(service.getItem('some.key3'), 'some.value3'); + assert.strictEqual(service.getItem('some.key4'), undefined); + }); + + test('Multiple ops (Immediate Strategy)', async function () { + const storageFile = join(testDir, 'storage.json'); + writeFileSync(storageFile, ''); + + let service = new FileStorage(URI.file(storageFile), SaveStrategy.IMMEDIATE, logService, fileService); + await service.init(); + + service.setItem('some.key1', 'some.value1'); + service.setItem('some.key2', 'some.value2'); + service.setItem('some.key3', 'some.value3'); + service.setItem('some.key4', 'some.value4'); + service.removeItem('some.key4'); + + assert.strictEqual(service.getItem('some.key1'), 'some.value1'); + assert.strictEqual(service.getItem('some.key2'), 'some.value2'); + assert.strictEqual(service.getItem('some.key3'), 'some.value3'); + assert.strictEqual(service.getItem('some.key4'), undefined); + + await service.close(); + + service = new FileStorage(URI.file(storageFile), SaveStrategy.IMMEDIATE, logService, fileService); await service.init(); assert.strictEqual(service.getItem('some.key1'), 'some.value1'); @@ -138,7 +229,7 @@ flakySuite('StateMainService', () => { const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); - const service = new FileStorage(URI.file(storageFile), logService, fileService); + const service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); service.setItem('some.key1', 'some.value1'); service.setItem('some.key2', 'some.value2'); @@ -163,7 +254,7 @@ flakySuite('StateMainService', () => { const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); - const service = new FileStorage(URI.file(storageFile), logService, fileService); + const service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); await service.init(); @@ -187,7 +278,7 @@ flakySuite('StateMainService', () => { const storageFile = join(testDir, 'storage.json'); writeFileSync(storageFile, ''); - const service = new FileStorage(URI.file(storageFile), logService, fileService); + const service = new FileStorage(URI.file(storageFile), SaveStrategy.DELAYED, logService, fileService); service.setItem('some.key1', 'some.value1'); service.setItem('some.key2', 'some.value2'); diff --git a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts index 1b42e2eafb6..487d16a7a09 100644 --- a/src/vs/platform/storage/test/electron-main/storageMainService.test.ts +++ b/src/vs/platform/storage/test/electron-main/storageMainService.test.ts @@ -15,7 +15,7 @@ import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifec import { NullLogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; -import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; import { IS_NEW_KEY, StorageScope } from 'vs/platform/storage/common/storage'; import { IStorageChangeEvent, IStorageMain, IStorageMainOptions } from 'vs/platform/storage/electron-main/storageMain'; import { StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; @@ -108,7 +108,7 @@ suite('StorageMainService', function () { function createStorageService(lifecycleMainService: ILifecycleMainService = new TestLifecycleMainService()): TestStorageMainService { const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService); const fileService = new FileService(new NullLogService()); - return new TestStorageMainService(new NullLogService(), environmentService, new UserDataProfilesMainService(new StateMainService(environmentService, new NullLogService(), fileService), new UriIdentityService(fileService), environmentService, fileService, new NullLogService()), lifecycleMainService, fileService, new UriIdentityService(fileService)); + return new TestStorageMainService(new NullLogService(), environmentService, new UserDataProfilesMainService(new StateService(SaveStrategy.DELAYED, environmentService, new NullLogService(), fileService), new UriIdentityService(fileService), environmentService, fileService, new NullLogService()), lifecycleMainService, fileService, new UriIdentityService(fileService)); } test('basics (application)', function () { diff --git a/src/vs/platform/telemetry/electron-main/telemetryUtils.ts b/src/vs/platform/telemetry/electron-main/telemetryUtils.ts index 35304508a9b..8736daf87ff 100644 --- a/src/vs/platform/telemetry/electron-main/telemetryUtils.ts +++ b/src/vs/platform/telemetry/electron-main/telemetryUtils.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { resolveMachineId as resolveNodeMachineId } from 'vs/platform/telemetry/node/telemetryUtils'; -export async function resolveMachineId(stateService: IStateMainService) { +export async function resolveMachineId(stateService: IStateService) { // Call the node layers implementation to avoid code duplication const machineId = await resolveNodeMachineId(stateService); stateService.setItem(machineIdKey, machineId); diff --git a/src/vs/platform/telemetry/node/telemetryUtils.ts b/src/vs/platform/telemetry/node/telemetryUtils.ts index 23731cd95b4..588ef5d9d4a 100644 --- a/src/vs/platform/telemetry/node/telemetryUtils.ts +++ b/src/vs/platform/telemetry/node/telemetryUtils.ts @@ -5,11 +5,11 @@ import { isMacintosh } from 'vs/base/common/platform'; import { getMachineId } from 'vs/base/node/id'; -import { IStateService } from 'vs/platform/state/node/state'; +import { IStateReadService } from 'vs/platform/state/node/state'; import { machineIdKey } from 'vs/platform/telemetry/common/telemetry'; -export async function resolveMachineId(stateService: IStateService) { +export async function resolveMachineId(stateService: IStateReadService) { // We cache the machineId for faster lookups // and resolve it only once initially if not cached or we need to replace the macOS iBridge device let machineId = stateService.getItem(machineIdKey); diff --git a/src/vs/platform/test/electron-main/workbenchTestServices.ts b/src/vs/platform/test/electron-main/workbenchTestServices.ts index a614fe43cd7..2d4d21be19a 100644 --- a/src/vs/platform/test/electron-main/workbenchTestServices.ts +++ b/src/vs/platform/test/electron-main/workbenchTestServices.ts @@ -7,7 +7,7 @@ import { Promises } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { ICodeWindow, UnloadReason } from 'vs/platform/window/electron-main/window'; export class TestLifecycleMainService implements ILifecycleMainService { @@ -49,7 +49,7 @@ export class TestLifecycleMainService implements ILifecycleMainService { async when(phase: LifecycleMainPhase): Promise { } } -export class InMemoryTestStateMainService implements IStateMainService { +export class InMemoryTestStateMainService implements IStateService { _serviceBrand: undefined; diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index caa1e0b6f45..9a6e96f3e87 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; import { IColorScheme } from 'vs/platform/window/common/window'; @@ -45,7 +45,7 @@ export class ThemeMainService extends Disposable implements IThemeMainService { private readonly _onDidChangeColorScheme = this._register(new Emitter()); readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event; - constructor(@IStateMainService private stateMainService: IStateMainService, @IConfigurationService private configurationService: IConfigurationService) { + constructor(@IStateService private stateService: IStateService, @IConfigurationService private configurationService: IConfigurationService) { super(); // Color Scheme changes @@ -84,9 +84,9 @@ export class ThemeMainService extends Disposable implements IThemeMainService { return colorScheme.dark ? DEFAULT_BG_HC_BLACK : DEFAULT_BG_HC_LIGHT; } - let background = this.stateMainService.getItem(THEME_BG_STORAGE_KEY, null); + let background = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); if (!background) { - const baseTheme = this.stateMainService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; + const baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; switch (baseTheme) { case 'vs': background = DEFAULT_BG_LIGHT; break; case 'hc-black': background = DEFAULT_BG_HC_BLACK; break; @@ -105,7 +105,7 @@ export class ThemeMainService extends Disposable implements IThemeMainService { saveWindowSplash(windowId: number | undefined, splash: IPartsSplash): void { // Update in storage - this.stateMainService.setItems([ + this.stateService.setItems([ { key: THEME_STORAGE_KEY, data: splash.baseTheme }, { key: THEME_BG_STORAGE_KEY, data: splash.colorInfo.background }, { key: THEME_WINDOW_SPLASH, data: splash } @@ -127,6 +127,6 @@ export class ThemeMainService extends Disposable implements IThemeMainService { } getWindowSplash(): IPartsSplash | undefined { - return this.stateMainService.getItem(THEME_WINDOW_SPLASH); + return this.stateService.getItem(THEME_WINDOW_SPLASH); } } diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index dc61da9c046..95348d22d24 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -4,17 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IUserDataProfilesService, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent, IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService, WillCreateProfileEvent, WillRemoveProfileEvent, IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; -import { IStringDictionary } from 'vs/base/common/collections'; import { IAnyWorkspaceIdentifier, IEmptyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { IStateService } from 'vs/platform/state/node/state'; export const IUserDataProfilesMainService = refineServiceDecorator(IUserDataProfilesService); export interface IUserDataProfilesMainService extends IUserDataProfilesService { @@ -28,13 +26,13 @@ export interface IUserDataProfilesMainService extends IUserDataProfilesService { export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService { constructor( - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService stateService: IStateService, @IUriIdentityService uriIdentityService: IUriIdentityService, @INativeEnvironmentService environmentService: INativeEnvironmentService, @IFileService fileService: IFileService, @ILogService logService: ILogService, ) { - super(stateMainService, uriIdentityService, environmentService, fileService, logService); + super(stateService, uriIdentityService, environmentService, fileService, logService); } getAssociatedEmptyWindows(): IEmptyWorkspaceIdentifier[] { @@ -45,48 +43,4 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme return emptyWindows; } - protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { - if (storedProfiles.length) { - this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles.map(profile => ({ ...profile, location: this.uriIdentityService.extUri.basename(profile.location) }))); - } else { - this.stateMainService.removeItem(UserDataProfilesMainService.PROFILES_KEY); - } - } - - protected override getStoredProfiles(): StoredUserDataProfile[] { - const storedProfiles = super.getStoredProfiles(); - if (!this.stateMainService.getItem('userDataProfilesMigration', false)) { - this.saveStoredProfiles(storedProfiles); - this.stateMainService.setItem('userDataProfilesMigration', true); - } - return storedProfiles; - } - - protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { - if (storedProfileAssociations.emptyWindows || storedProfileAssociations.workspaces) { - this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, storedProfileAssociations); - } else { - this.stateMainService.removeItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY); - } - } - - protected override getStoredProfileAssociations(): StoredProfileAssociations { - const oldKey = 'workspaceAndProfileInfo'; - const storedWorkspaceInfos = this.stateMainService.getItem<{ workspace: UriComponents; profile: UriComponents }[]>(oldKey, undefined); - if (storedWorkspaceInfos) { - this.stateMainService.removeItem(oldKey); - const workspaces = storedWorkspaceInfos.reduce>((result, { workspace, profile }) => { - result[URI.revive(workspace).toString()] = URI.revive(profile).toString(); - return result; - }, {}); - this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces }); - } - const associations = super.getStoredProfileAssociations(); - if (!this.stateMainService.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, false)) { - this.saveStoredProfileAssociations(associations); - this.stateMainService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, true); - } - return associations; - } - } diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts index 5dcc1f5d994..b5fc85922e9 100644 --- a/src/vs/platform/userDataProfile/node/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -3,56 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isString } from 'vs/base/common/types'; -import { URI, UriDto } from 'vs/base/common/uri'; +import { URI, UriComponents, UriDto } from 'vs/base/common/uri'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateService } from 'vs/platform/state/node/state'; +import { IStateReadService, IStateService } from 'vs/platform/state/node/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, StoredUserDataProfile, StoredProfileAssociations } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { isString } from 'vs/base/common/types'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; -export class ServerUserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService { +type StoredUserDataProfileState = StoredUserDataProfile & { location: URI | string }; + +export class UserDataProfilesReadonlyService extends BaseUserDataProfilesService implements IUserDataProfilesService { + + protected static readonly PROFILE_ASSOCIATIONS_MIGRATION_KEY = 'profileAssociationsMigration'; constructor( + @IStateReadService private readonly stateReadonlyService: IStateReadService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @INativeEnvironmentService protected readonly nativeEnvironmentService: INativeEnvironmentService, + @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, @IFileService fileService: IFileService, @ILogService logService: ILogService, ) { super(nativeEnvironmentService, fileService, uriIdentityService, logService); } - protected override getDefaultProfileExtensionsLocation(): URI { - return this.uriIdentityService.extUri.joinPath(URI.file(this.nativeEnvironmentService.extensionsPath).with({ scheme: this.profilesHome.scheme }), 'extensions.json'); - } - -} - -type StoredUserDataProfileState = StoredUserDataProfile & { location: URI | string }; - -export class UserDataProfilesService extends ServerUserDataProfilesService implements IUserDataProfilesService { - - protected static readonly PROFILE_ASSOCIATIONS_MIGRATION_KEY = 'profileAssociationsMigration'; - - constructor( - @IStateService private readonly stateService: IStateService, - @IUriIdentityService uriIdentityService: IUriIdentityService, - @INativeEnvironmentService nativeEnvironmentService: INativeEnvironmentService, - @IFileService fileService: IFileService, - @ILogService logService: ILogService, - ) { - super(uriIdentityService, nativeEnvironmentService, fileService, logService); - } - protected override getStoredProfiles(): StoredUserDataProfile[] { - const storedProfilesState = this.stateService.getItem[]>(UserDataProfilesService.PROFILES_KEY, []); + const storedProfilesState = this.stateReadonlyService.getItem[]>(UserDataProfilesReadonlyService.PROFILES_KEY, []); return storedProfilesState.map(p => ({ ...p, location: isString(p.location) ? this.uriIdentityService.extUri.joinPath(this.profilesHome, p.location) : URI.revive(p.location) })); } protected override getStoredProfileAssociations(): StoredProfileAssociations { - const associations = this.stateService.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, {}); - const migrated = this.stateService.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, false); + const associations = this.stateReadonlyService.getItem(UserDataProfilesReadonlyService.PROFILE_ASSOCIATIONS_KEY, {}); + const migrated = this.stateReadonlyService.getItem(UserDataProfilesReadonlyService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, false); return migrated ? associations : this.migrateStoredProfileAssociations(associations); } @@ -61,3 +46,78 @@ export class UserDataProfilesService extends ServerUserDataProfilesService imple } } + +export class UserDataProfilesService extends UserDataProfilesReadonlyService implements IUserDataProfilesService { + + constructor( + @IStateService protected readonly stateService: IStateService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @INativeEnvironmentService environmentService: INativeEnvironmentService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService, + ) { + super(stateService, uriIdentityService, environmentService, fileService, logService); + } + + protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { + if (storedProfiles.length) { + this.stateService.setItem(UserDataProfilesService.PROFILES_KEY, storedProfiles.map(profile => ({ ...profile, location: this.uriIdentityService.extUri.basename(profile.location) }))); + } else { + this.stateService.removeItem(UserDataProfilesService.PROFILES_KEY); + } + } + + protected override getStoredProfiles(): StoredUserDataProfile[] { + const storedProfiles = super.getStoredProfiles(); + if (!this.stateService.getItem('userDataProfilesMigration', false)) { + this.saveStoredProfiles(storedProfiles); + this.stateService.setItem('userDataProfilesMigration', true); + } + return storedProfiles; + } + + protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { + if (storedProfileAssociations.emptyWindows || storedProfileAssociations.workspaces) { + this.stateService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, storedProfileAssociations); + } else { + this.stateService.removeItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY); + } + } + + protected override getStoredProfileAssociations(): StoredProfileAssociations { + const oldKey = 'workspaceAndProfileInfo'; + const storedWorkspaceInfos = this.stateService.getItem<{ workspace: UriComponents; profile: UriComponents }[]>(oldKey, undefined); + if (storedWorkspaceInfos) { + this.stateService.removeItem(oldKey); + const workspaces = storedWorkspaceInfos.reduce>((result, { workspace, profile }) => { + result[URI.revive(workspace).toString()] = URI.revive(profile).toString(); + return result; + }, {}); + this.stateService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, { workspaces }); + } + const associations = super.getStoredProfileAssociations(); + if (!this.stateService.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, false)) { + this.saveStoredProfileAssociations(associations); + this.stateService.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_MIGRATION_KEY, true); + } + return associations; + } +} + +export class ServerUserDataProfilesService extends UserDataProfilesService implements IUserDataProfilesService { + + constructor( + @IUriIdentityService uriIdentityService: IUriIdentityService, + @INativeEnvironmentService environmentService: INativeEnvironmentService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService, + ) { + super(new StateService(SaveStrategy.IMMEDIATE, environmentService, logService, fileService), uriIdentityService, environmentService, fileService, logService); + } + + override async init(): Promise { + await (this.stateService as StateService).init(); + return super.init(); + } + +} diff --git a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts index 84f386750c0..d2ca96156f5 100644 --- a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts +++ b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts @@ -14,7 +14,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; -import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -32,7 +32,7 @@ suite('UserDataProfileMainService', () => { const disposables = new DisposableStore(); let testObject: UserDataProfilesMainService; - let environmentService: TestEnvironmentService, stateService: StateMainService; + let environmentService: TestEnvironmentService, stateService: StateService; setup(async () => { const logService = new NullLogService(); @@ -41,7 +41,7 @@ suite('UserDataProfileMainService', () => { disposables.add(fileService.registerProvider(Schemas.vscodeUserData, fileSystemProvider)); environmentService = new TestEnvironmentService(joinPath(ROOT, 'User')); - stateService = new StateMainService(environmentService, logService, fileService); + stateService = new StateService(SaveStrategy.DELAYED, environmentService, logService, fileService); testObject = new UserDataProfilesMainService(stateService, new UriIdentityService(fileService), environmentService, fileService, logService); await stateService.init(); diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index cf0fa2adb10..e2f045991fc 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -40,7 +40,7 @@ import { IWindowState, ICodeWindow, ILoadEvent, WindowMode, WindowError, LoadRea import { Color } from 'vs/base/common/color'; import { IPolicyService } from 'vs/platform/policy/common/policy'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { INativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; @@ -200,7 +200,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IProductService private readonly productService: IProductService, @IProtocolMainService private readonly protocolMainService: IProtocolMainService, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @INativeHostMainService private readonly nativeHostMainService: INativeHostMainService ) { super(); @@ -220,7 +220,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (typeof CodeWindow.sandboxState === 'undefined') { // we should only check this once so that we do not end up // with some windows in sandbox mode and some not! - CodeWindow.sandboxState = this.stateMainService.getItem('window.experimental.useSandbox', false); + CodeWindow.sandboxState = this.stateService.getItem('window.experimental.useSandbox', false); } const windowSettings = this.configurationService.getValue('window'); @@ -335,7 +335,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Update the window controls immediately based on cached values if (useCustomTitleStyle && ((isWindows && useWindowControlsOverlay(this.configurationService)) || isMacintosh)) { - const cachedWindowControlHeight = this.stateMainService.getItem((CodeWindow.windowControlHeightStateStorageKey)); + const cachedWindowControlHeight = this.stateService.getItem((CodeWindow.windowControlHeightStateStorageKey)); if (cachedWindowControlHeight) { this.updateWindowControls({ height: cachedWindowControlHeight }); } @@ -794,7 +794,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } const { installSourcePath } = this.environmentMainService; - const machineId = await resolveMachineId(this.stateMainService); + const machineId = await resolveMachineId(this.stateService); const config: ITelemetryServiceConfig = { appenders, @@ -1197,7 +1197,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Cache the height for speeds lookups on startup if (options.height) { - this.stateMainService.setItem((CodeWindow.windowControlHeightStateStorageKey), options.height); + this.stateService.setItem((CodeWindow.windowControlHeightStateStorageKey), options.height); } // Windows: window control overlay (WCO) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index cd0840c9135..cd88dd722c1 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -36,7 +36,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { IAddFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from 'vs/platform/window/common/window'; import { CodeWindow } from 'vs/platform/windows/electron-main/windowImpl'; import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; @@ -195,14 +195,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: ICodeWindow; x: number; y: number }>()); readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event; - private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateMainService, this.lifecycleMainService, this.logService, this.configurationService)); + private readonly windowsStateHandler = this._register(new WindowsStateHandler(this, this.stateService, this.lifecycleMainService, this.logService, this.configurationService)); constructor( private readonly machineId: string, private readonly initialUserEnv: IProcessEnvironment, @ILogService private readonly logService: ILogService, @ILoggerMainService private readonly loggerService: ILoggerMainService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @IPolicyService private readonly policyService: IPolicyService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, diff --git a/src/vs/platform/windows/electron-main/windowsStateHandler.ts b/src/vs/platform/windows/electron-main/windowsStateHandler.ts index a4de6b2bb81..a020ac049ae 100644 --- a/src/vs/platform/windows/electron-main/windowsStateHandler.ts +++ b/src/vs/platform/windows/electron-main/windowsStateHandler.ts @@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/window/common/window'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { defaultWindowState, ICodeWindow, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/window/electron-main/window'; @@ -55,7 +55,7 @@ export class WindowsStateHandler extends Disposable { private static readonly windowsStateStorageKey = 'windowsState'; get state() { return this._state; } - private readonly _state = restoreWindowsState(this.stateMainService.getItem(WindowsStateHandler.windowsStateStorageKey)); + private readonly _state = restoreWindowsState(this.stateService.getItem(WindowsStateHandler.windowsStateStorageKey)); private lastClosedState: IWindowState | undefined = undefined; @@ -63,7 +63,7 @@ export class WindowsStateHandler extends Disposable { constructor( @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -214,7 +214,7 @@ export class WindowsStateHandler extends Disposable { // Persist const state = getWindowsStateStoreData(currentWindowsState); - this.stateMainService.setItem(WindowsStateHandler.windowsStateStorageKey, state); + this.stateService.setItem(WindowsStateHandler.windowsStateStorageKey, state); if (this.shuttingDown) { this.logService.trace('[WindowsStateHandler] onBeforeShutdown', state); diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts index faa6920f00c..d6b76495b43 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts @@ -25,7 +25,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; -import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; import { IRawFileWorkspaceFolder, IRawUriWorkspaceFolder, WORKSPACE_EXTENSION } from 'vs/platform/workspace/common/workspace'; @@ -109,7 +109,7 @@ flakySuite('WorkspacesManagementMainService', () => { const logService = new NullLogService(); const fileService = new FileService(logService); - service = new WorkspacesManagementMainService(environmentMainService, logService, new UserDataProfilesMainService(new StateMainService(environmentMainService, logService, fileService), new UriIdentityService(fileService), environmentMainService, fileService, logService), new TestBackupMainService(), new TestDialogMainService()); + service = new WorkspacesManagementMainService(environmentMainService, logService, new UserDataProfilesMainService(new StateService(SaveStrategy.DELAYED, environmentMainService, logService, fileService), new UriIdentityService(fileService), environmentMainService, fileService, logService), new TestBackupMainService(), new TestDialogMainService()); return pfs.Promises.mkdir(untitledWorkspacesHomePath, { recursive: true }); }); diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 8c35f399e3e..f3512d89ed3 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -105,9 +105,14 @@ class CliMain extends Disposable { // Configuration const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService)); - await configurationService.initialize(); services.set(IConfigurationService, configurationService); + // Initialize + await Promise.all([ + configurationService.initialize(), + userDataProfilesService.init() + ]); + services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IDownloadService, new SyncDescriptor(DownloadService)); services.set(ITelemetryService, NullTelemetryService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index a4c9608397b..c51c166cf51 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -123,14 +123,20 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); + // Configuration + const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService(), logService); + services.set(IConfigurationService, configurationService); + // User Data Profiles const userDataProfilesService = new ServerUserDataProfilesService(uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); - // Configuration - const configurationService = new ConfigurationService(environmentService.machineSettingsResource, fileService, new NullPolicyService(), logService); - services.set(IConfigurationService, configurationService); - await configurationService.initialize(); + // Initialize + const [, , machineId] = await Promise.all([ + configurationService.initialize(), + userDataProfilesService.init(), + getMachineId() + ]); const extensionHostStatusService = new ExtensionHostStatusService(); services.set(IExtensionHostStatusService, extensionHostStatusService); @@ -139,7 +145,6 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IRequestService, new SyncDescriptor(RequestService)); let oneDsAppender: ITelemetryAppender = NullAppender; - const machineId = await getMachineId(); const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) {