diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index ece4482cfbb..40d93c2010b 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -47,6 +47,8 @@ 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 { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; @@ -55,7 +57,8 @@ import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppen 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 { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; class CliMain extends Disposable { @@ -133,8 +136,16 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); 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(environmentService, fileService, logService); + const userDataProfilesService = new UserDataProfilesService(stateService, uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); // Policy @@ -147,8 +158,11 @@ class CliMain extends Disposable { const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, policyService, logService)); services.set(IConfigurationService, configurationService); - // Init config - await configurationService.initialize(); + // Initialize + await Promise.all([ + stateService.init().then(() => userDataProfilesService.init()), + configurationService.initialize() + ]); // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); diff --git a/src/vs/platform/state/electron-main/state.ts b/src/vs/platform/state/electron-main/state.ts index d50e7888a41..c215cde7ac6 100644 --- a/src/vs/platform/state/electron-main/state.ts +++ b/src/vs/platform/state/electron-main/state.ts @@ -4,16 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IStateService } from 'vs/platform/state/node/state'; export const IStateMainService = createDecorator('stateMainService'); -export interface IStateMainService { +export interface IStateMainService extends IStateService { readonly _serviceBrand: undefined; - getItem(key: string, defaultValue: T): T; - getItem(key: string, defaultValue?: T): T | 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; diff --git a/src/vs/platform/state/electron-main/stateMainService.ts b/src/vs/platform/state/electron-main/stateMainService.ts index 714e2cbdae3..3235a351ff4 100644 --- a/src/vs/platform/state/electron-main/stateMainService.ts +++ b/src/vs/platform/state/electron-main/stateMainService.ts @@ -3,171 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ThrottledDelayer } from 'vs/base/common/async'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; -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 { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { StateService } from 'vs/platform/state/node/stateService'; -type StorageDatabase = { [key: string]: unknown }; - -export class FileStorage { - - private storage: StorageDatabase = Object.create(null); - private lastSavedStorageContents = ''; - - private readonly flushDelayer = new ThrottledDelayer(100 /* buffer saves over a short time */); - - private initializing: Promise | undefined = undefined; - private closing: Promise | undefined = undefined; - - constructor( - private readonly storagePath: URI, - private readonly logService: ILogService, - private readonly fileService: IFileService - ) { - } - - init(): Promise { - if (!this.initializing) { - this.initializing = this.doInit(); - } - - return this.initializing; - } - - private async doInit(): Promise { - try { - this.lastSavedStorageContents = (await this.fileService.readFile(this.storagePath)).value.toString(); - this.storage = JSON.parse(this.lastSavedStorageContents); - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - this.logService.error(error); - } - } - } - - getItem(key: string, defaultValue: T): T; - getItem(key: string, defaultValue?: T): T | undefined; - getItem(key: string, defaultValue?: T): T | undefined { - const res = this.storage[key]; - if (isUndefinedOrNull(res)) { - return defaultValue; - } - - return res as T; - } - - setItem(key: string, data?: object | string | number | boolean | undefined | null): void { - this.setItems([{ key, data }]); - } - - setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void { - let save = false; - - for (const { key, data } of items) { - - // Shortcut for data that did not change - if (this.storage[key] === data) { - continue; - } - - // Remove items when they are undefined or null - if (isUndefinedOrNull(data)) { - if (!isUndefined(this.storage[key])) { - this.storage[key] = undefined; - save = true; - } - } - - // Otherwise add an item - else { - this.storage[key] = data; - save = true; - } - } - - if (save) { - this.save(); - } - } - - removeItem(key: string): void { - - // Only update if the key is actually present (not undefined) - if (!isUndefined(this.storage[key])) { - this.storage[key] = undefined; - this.save(); - } - } - - private async save(delay?: number): Promise { - if (this.closing) { - return; // already about to close - } - - return this.flushDelayer.trigger(() => this.doSave(), delay); - } - - private async doSave(): Promise { - if (!this.initializing) { - return; // if we never initialized, we should not save our state - } - - // Make sure to wait for init to finish first - await this.initializing; - - // Return early if the database has not changed - const serializedDatabase = JSON.stringify(this.storage, null, 4); - if (serializedDatabase === this.lastSavedStorageContents) { - return; - } - - // Write to disk - try { - await this.fileService.writeFile(this.storagePath, VSBuffer.fromString(serializedDatabase)); - this.lastSavedStorageContents = serializedDatabase; - } catch (error) { - this.logService.error(error); - } - } - - async close(): Promise { - if (!this.closing) { - this.closing = this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */); - } - - return this.closing; - } -} - -export class StateMainService implements IStateMainService { +export class StateMainService extends StateService implements IStateMainService { declare readonly _serviceBrand: undefined; - private readonly fileStorage: FileStorage; - - constructor( - @IEnvironmentService environmentService: IEnvironmentService, - @ILogService logService: ILogService, - @IFileService fileService: IFileService - ) { - this.fileStorage = new FileStorage(environmentService.stateResource, logService, fileService); - } - - async init(): Promise { - return this.fileStorage.init(); - } - - getItem(key: string, defaultValue: T): T; - getItem(key: string, defaultValue?: T): T | undefined; - getItem(key: string, defaultValue?: T): T | undefined { - return this.fileStorage.getItem(key, defaultValue); - } - setItem(key: string, data?: object | string | number | boolean | undefined | null): void { this.fileStorage.setItem(key, data); } diff --git a/src/vs/platform/state/node/state.ts b/src/vs/platform/state/node/state.ts new file mode 100644 index 00000000000..a02b3990bf7 --- /dev/null +++ b/src/vs/platform/state/node/state.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +export const IStateService = createDecorator('stateService'); + +export interface IStateService { + + readonly _serviceBrand: undefined; + + getItem(key: string, defaultValue: T): T; + getItem(key: string, defaultValue?: T): T | undefined; + +} diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts new file mode 100644 index 00000000000..67ca7ef32be --- /dev/null +++ b/src/vs/platform/state/node/stateService.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ThrottledDelayer } from 'vs/base/common/async'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; +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'; + +type StorageDatabase = { [key: string]: unknown }; + +export class FileStorage { + + private storage: StorageDatabase = Object.create(null); + private lastSavedStorageContents = ''; + + private readonly flushDelayer = new ThrottledDelayer(100 /* buffer saves over a short time */); + + private initializing: Promise | undefined = undefined; + private closing: Promise | undefined = undefined; + + constructor( + private readonly storagePath: URI, + private readonly logService: ILogService, + private readonly fileService: IFileService + ) { + } + + init(): Promise { + if (!this.initializing) { + this.initializing = this.doInit(); + } + + return this.initializing; + } + + private async doInit(): Promise { + try { + this.lastSavedStorageContents = (await this.fileService.readFile(this.storagePath)).value.toString(); + this.storage = JSON.parse(this.lastSavedStorageContents); + } catch (error) { + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { + this.logService.error(error); + } + } + } + + getItem(key: string, defaultValue: T): T; + getItem(key: string, defaultValue?: T): T | undefined; + getItem(key: string, defaultValue?: T): T | undefined { + const res = this.storage[key]; + if (isUndefinedOrNull(res)) { + return defaultValue; + } + + return res as T; + } + + setItem(key: string, data?: object | string | number | boolean | undefined | null): void { + this.setItems([{ key, data }]); + } + + setItems(items: readonly { key: string; data?: object | string | number | boolean | undefined | null }[]): void { + let save = false; + + for (const { key, data } of items) { + + // Shortcut for data that did not change + if (this.storage[key] === data) { + continue; + } + + // Remove items when they are undefined or null + if (isUndefinedOrNull(data)) { + if (!isUndefined(this.storage[key])) { + this.storage[key] = undefined; + save = true; + } + } + + // Otherwise add an item + else { + this.storage[key] = data; + save = true; + } + } + + if (save) { + this.save(); + } + } + + removeItem(key: string): void { + + // Only update if the key is actually present (not undefined) + if (!isUndefined(this.storage[key])) { + this.storage[key] = undefined; + this.save(); + } + } + + private async save(delay?: number): Promise { + if (this.closing) { + return; // already about to close + } + + return this.flushDelayer.trigger(() => this.doSave(), delay); + } + + private async doSave(): Promise { + if (!this.initializing) { + return; // if we never initialized, we should not save our state + } + + // Make sure to wait for init to finish first + await this.initializing; + + // Return early if the database has not changed + const serializedDatabase = JSON.stringify(this.storage, null, 4); + if (serializedDatabase === this.lastSavedStorageContents) { + return; + } + + // Write to disk + try { + await this.fileService.writeFile(this.storagePath, VSBuffer.fromString(serializedDatabase)); + this.lastSavedStorageContents = serializedDatabase; + } catch (error) { + this.logService.error(error); + } + } + + async close(): Promise { + if (!this.closing) { + this.closing = this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */); + } + + return this.closing; + } +} + +export class StateService implements IStateService { + + declare readonly _serviceBrand: undefined; + + protected readonly fileStorage: FileStorage; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @ILogService logService: ILogService, + @IFileService fileService: IFileService + ) { + this.fileStorage = new FileStorage(environmentService.stateResource, logService, fileService); + } + + async init(): Promise { + return this.fileStorage.init(); + } + + getItem(key: string, defaultValue: T): T; + getItem(key: string, defaultValue?: T): T | undefined; + getItem(key: string, defaultValue?: T): T | undefined { + return this.fileStorage.getItem(key, defaultValue); + } + +} diff --git a/src/vs/platform/state/test/electron-main/state.test.ts b/src/vs/platform/state/test/electron-main/state.test.ts index c043c308a12..a37f31b3100 100644 --- a/src/vs/platform/state/test/electron-main/state.test.ts +++ b/src/vs/platform/state/test/electron-main/state.test.ts @@ -15,7 +15,7 @@ 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/electron-main/stateMainService'; +import { FileStorage } from 'vs/platform/state/node/stateService'; flakySuite('StateMainService', () => { diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index 0d3d4f2016e..c798c226ef5 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -3,10 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ResourceMap } from 'vs/base/common/map'; import { Emitter, Event } from 'vs/base/common/event'; -import { revive } from 'vs/base/common/marshalling'; -import { UriDto } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -14,9 +11,10 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant 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 { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService, reviveProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { Promises } from 'vs/base/common/async'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; export type WillCreateProfileEvent = { profile: IUserDataProfile; @@ -34,11 +32,6 @@ export interface IUserDataProfilesMainService extends IUserDataProfilesService { readonly onWillRemoveProfile: Event; } -type UserDataProfilesObject = { - profiles: IUserDataProfile[]; - workspaces: ResourceMap; -}; - type StoredUserDataProfile = { name: string; location: URI; @@ -52,9 +45,6 @@ type StoredWorkspaceInfo = { export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService { - private static readonly PROFILES_KEY = 'userDataProfiles'; - private static readonly WORKSPACE_PROFILE_INFO_KEY = 'workspaceAndProfileInfo'; - private readonly _onWillCreateProfile = this._register(new Emitter()); readonly onWillCreateProfile = this._onWillCreateProfile.event; @@ -63,46 +53,17 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme constructor( @IStateMainService private readonly stateMainService: IStateMainService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @ILogService logService: ILogService, ) { - super(environmentService, fileService, logService); - } - - init(): void { - this._profilesObject = undefined; - } - - private _profilesObject: UserDataProfilesObject | undefined; - private get profilesObject(): UserDataProfilesObject { - if (!this._profilesObject) { - const profiles = this.storedProfiles.map(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)); - const workspaces = new ResourceMap(); - if (profiles.length) { - profiles.unshift(this.createDefaultUserDataProfile(true)); - for (const workspaceProfileInfo of this.storedWorskpaceInfos) { - const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, workspaceProfileInfo.profile)); - if (profile) { - workspaces.set(workspaceProfileInfo.workspace, profile); - } - } - } - this._profilesObject = { profiles, workspaces }; - } - return this._profilesObject; - } - - override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; } - - override getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile { - return this.profilesObject.workspaces.get(this.getWorkspace(workspaceIdentifier)) ?? this.defaultProfile; + super(stateMainService, uriIdentityService, environmentService, fileService, logService); } override async createProfile(profile: IUserDataProfile, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { profile = reviveProfile(profile, this.profilesHome.scheme); - if (this.storedProfiles.some(p => p.name === profile.name)) { + if (this.getStoredProfiles().some(p => p.name === profile.name)) { throw new Error(`Profile with name ${profile.name} already exists`); } @@ -120,8 +81,8 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme await Promises.settled(joiners); const storedProfile: StoredUserDataProfile = { name: profile.name, location: profile.location, useDefaultFlags: profile.useDefaultFlags }; - const storedProfiles = [...this.storedProfiles, storedProfile]; - this.storedProfiles = storedProfiles; + const storedProfiles = [...this.getStoredProfiles(), storedProfile]; + this.setStoredProfiles(storedProfiles); if (workspaceIdentifier) { await this.setProfileForWorkspace(profile, workspaceIdentifier); } @@ -131,24 +92,20 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise { profile = reviveProfile(profile, this.profilesHome.scheme); const workspace = this.getWorkspace(workspaceIdentifier); - const storedWorkspaceInfos = this.storedWorskpaceInfos.filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace)); + const storedWorkspaceInfos = this.getStoredWorskpaceInfos().filter(info => !this.uriIdentityService.extUri.isEqual(info.workspace, workspace)); if (!profile.isDefault) { storedWorkspaceInfos.push({ workspace, profile: profile.location }); } - this.storedWorskpaceInfos = storedWorkspaceInfos; + this.setStoredWorskpaceInfos(storedWorkspaceInfos); return this.profilesObject.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))!; } - private getWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier) { - return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) ? workspaceIdentifier.uri : workspaceIdentifier.configPath; - } - override async removeProfile(profile: IUserDataProfile): Promise { if (profile.isDefault) { throw new Error('Cannot remove default profile'); } profile = reviveProfile(profile, this.profilesHome.scheme); - if (!this.storedProfiles.some(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))) { + if (!this.getStoredProfiles().some(p => this.uriIdentityService.extUri.isEqual(p.location, profile.location))) { throw new Error(`Profile with name ${profile.name} does not exist`); } @@ -167,25 +124,17 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme await this.fileService.del(profile.location, { recursive: true }); } - this.storedWorskpaceInfos = this.storedWorskpaceInfos.filter(p => !this.uriIdentityService.extUri.isEqual(p.profile, profile.location)); - this.storedProfiles = this.storedProfiles.filter(p => !this.uriIdentityService.extUri.isEqual(p.location, profile.location)); + this.setStoredWorskpaceInfos(this.getStoredWorskpaceInfos().filter(p => !this.uriIdentityService.extUri.isEqual(p.profile, profile.location))); + this.setStoredProfiles(this.getStoredProfiles().filter(p => !this.uriIdentityService.extUri.isEqual(p.location, profile.location))); } - private get storedProfiles(): StoredUserDataProfile[] { - return revive(this.stateMainService.getItem[]>(UserDataProfilesMainService.PROFILES_KEY, [])); - } - - private set storedProfiles(storedProfiles: StoredUserDataProfile[]) { + private setStoredProfiles(storedProfiles: StoredUserDataProfile[]) { this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles); this._profilesObject = undefined; this._onDidChangeProfiles.fire(this.profiles); } - private get storedWorskpaceInfos(): StoredWorkspaceInfo[] { - return revive(this.stateMainService.getItem[]>(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, [])); - } - - private set storedWorskpaceInfos(storedWorkspaceInfos: StoredWorkspaceInfo[]) { + private setStoredWorskpaceInfos(storedWorkspaceInfos: StoredWorkspaceInfo[]) { this.stateMainService.setItem(UserDataProfilesMainService.WORKSPACE_PROFILE_INFO_KEY, storedWorkspaceInfos); this._profilesObject = undefined; } diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts new file mode 100644 index 00000000000..0f7962b5c74 --- /dev/null +++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResourceMap } from 'vs/base/common/map'; +import { revive } from 'vs/base/common/marshalling'; +import { UriDto } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } 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 { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; + +type UserDataProfilesObject = { + profiles: IUserDataProfile[]; + workspaces: ResourceMap; +}; + +type StoredUserDataProfile = { + name: string; + location: URI; + useDefaultFlags?: UseDefaultProfileFlags; +}; + +type StoredWorkspaceInfo = { + workspace: URI; + profile: URI; +}; + +export class UserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService { + + protected static readonly PROFILES_KEY = 'userDataProfiles'; + protected static readonly WORKSPACE_PROFILE_INFO_KEY = 'workspaceAndProfileInfo'; + + constructor( + @IStateService private readonly stateService: IStateService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService, + ) { + super(environmentService, fileService, logService); + } + + init(): void { + this._profilesObject = undefined; + } + + protected _profilesObject: UserDataProfilesObject | undefined; + protected get profilesObject(): UserDataProfilesObject { + if (!this._profilesObject) { + const profiles = this.getStoredProfiles().map(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)); + const workspaces = new ResourceMap(); + if (profiles.length) { + profiles.unshift(this.createDefaultUserDataProfile(true)); + for (const workspaceProfileInfo of this.getStoredWorskpaceInfos()) { + const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, workspaceProfileInfo.profile)); + if (profile) { + workspaces.set(workspaceProfileInfo.workspace, profile); + } + } + } + this._profilesObject = { profiles, workspaces }; + } + return this._profilesObject; + } + + override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; } + + override getProfile(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): IUserDataProfile { + return this.profilesObject.workspaces.get(this.getWorkspace(workspaceIdentifier)) ?? this.defaultProfile; + } + + protected getWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier) { + return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) ? workspaceIdentifier.uri : workspaceIdentifier.configPath; + } + + protected getStoredProfiles(): StoredUserDataProfile[] { + return revive(this.stateService.getItem[]>(UserDataProfilesService.PROFILES_KEY, [])); + } + + protected getStoredWorskpaceInfos(): StoredWorkspaceInfo[] { + return revive(this.stateService.getItem[]>(UserDataProfilesService.WORKSPACE_PROFILE_INFO_KEY, [])); + } + +}