Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/vs/platform/userDataSync')
-rw-r--r--src/vs/platform/userDataSync/common/globalStateSync.ts14
-rw-r--r--src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts151
-rw-r--r--src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts107
-rw-r--r--src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts52
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncClient.ts10
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts119
6 files changed, 441 insertions, 12 deletions
diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts
index 8371dabb50d..1f7f2860565 100644
--- a/src/vs/platform/userDataSync/common/globalStateSync.ts
+++ b/src/vs/platform/userDataSync/common/globalStateSync.ts
@@ -28,7 +28,7 @@ import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, ISyncResourceHandle, IUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
-import { IProfileStorageService } from 'vs/platform/storage/common/profileStorageService';
+import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
const argvStoragePrefx = 'globalState.argv.';
const argvProperties: string[] = ['locale'];
@@ -77,7 +77,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
constructor(
private readonly profile: IUserDataProfile,
- @IProfileStorageService private readonly profileStorageService: IProfileStorageService,
+ @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService,
@IFileService fileService: IFileService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@@ -95,7 +95,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
Event.any(
/* Locale change */
Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
- Event.filter(this.profileStorageService.onDidChange, e => {
+ Event.filter(this.userDataSyncProfilesStorageService.onDidChange, e => {
/* StorageTarget has changed in profile storage */
if (e.targetChanges.some(profile => this.profile.id === profile.id)) {
return true;
@@ -311,7 +311,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
}
}
- const storageData = await this.profileStorageService.readStorageData(this.profile);
+ const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.profile);
for (const [key, value] of storageData) {
if (value.value && value.target === StorageTarget.USER) {
storage[key] = { version: 1, value: value.value };
@@ -335,7 +335,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>; updated: IStringDictionary<IStorageValue>; removed: string[] }): Promise<void> {
const argv: IStringDictionary<any> = {};
const updatedStorage = new Map<string, string | undefined>();
- const storageData = await this.profileStorageService.readStorageData(this.profile);
+ const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.profile);
const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary<IStorageValue>): void => {
for (const key of keys) {
if (key.startsWith(argvStoragePrefx)) {
@@ -364,7 +364,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
if (updatedStorage.size) {
this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`);
- await this.profileStorageService.updateStorageData(this.profile, updatedStorage, StorageTarget.USER);
+ await this.userDataSyncProfilesStorageService.updateStorageData(this.profile, updatedStorage, StorageTarget.USER);
this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, [...updatedStorage.keys()]);
}
}
@@ -383,7 +383,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise<StorageKeys> {
- const storageData = await this.profileStorageService.readStorageData(this.profile);
+ const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.profile);
const user: string[] = [], machine: string[] = [];
for (const [key, value] of storageData) {
if (value.target === StorageTarget.USER) {
diff --git a/src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts b/src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts
new file mode 100644
index 00000000000..f79a4f4a894
--- /dev/null
+++ b/src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts
@@ -0,0 +1,151 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Event } from 'vs/base/common/event';
+import { Disposable, isDisposable } from 'vs/base/common/lifecycle';
+import { IStorage, IStorageDatabase, Storage } from 'vs/base/parts/storage/common/storage';
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { AbstractStorageService, IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+export interface IProfileStorageValueChanges {
+ readonly profile: IUserDataProfile;
+ readonly changes: IStorageValueChangeEvent[];
+}
+
+export interface IProfileStorageChanges {
+ readonly targetChanges: IUserDataProfile[];
+ readonly valueChanges: IProfileStorageValueChanges[];
+}
+
+export interface IStorageValue {
+ readonly value: string | undefined;
+ readonly target: StorageTarget;
+}
+
+export const IUserDataSyncProfilesStorageService = createDecorator<IUserDataSyncProfilesStorageService>('IUserDataSyncProfilesStorageService');
+export interface IUserDataSyncProfilesStorageService {
+ readonly _serviceBrand: undefined;
+
+ /**
+ * Emitted whenever data is updated or deleted in a profile storage or target of a profile storage entry changes
+ */
+ readonly onDidChange: Event<IProfileStorageChanges>;
+
+ /**
+ * Return the requested profile storage data
+ * @param profile The profile from which the data has to be read from
+ */
+ readStorageData(profile: IUserDataProfile): Promise<Map<string, IStorageValue>>;
+
+ /**
+ * Update the given profile storage data in the profile storage
+ * @param profile The profile to which the data has to be written to
+ * @param data Data that has to be updated
+ * @param target Storage target of the data
+ */
+ updateStorageData(profile: IUserDataProfile, data: Map<string, string | undefined | null>, target: StorageTarget): Promise<void>;
+}
+
+export abstract class AbstractUserDataSyncProfilesStorageService extends Disposable implements IUserDataSyncProfilesStorageService {
+
+ _serviceBrand: undefined;
+
+ readonly abstract onDidChange: Event<IProfileStorageChanges>;
+
+ constructor(
+ @IStorageService protected readonly storageService: IStorageService
+ ) {
+ super();
+ }
+
+ async readStorageData(profile: IUserDataProfile): Promise<Map<string, IStorageValue>> {
+ // Use current storage service if the profile is same
+ if (this.storageService.isProfileStorageFor(profile)) {
+ return this.getItems(this.storageService);
+ }
+
+ const storageDatabase = await this.createStorageDatabase(profile);
+ const storageService = new StorageService(storageDatabase);
+ try {
+ await storageService.initialize();
+ return this.getItems(storageService);
+ } finally {
+ storageService.dispose();
+ await this.closeAndDispose(storageDatabase);
+ }
+ }
+
+ async updateStorageData(profile: IUserDataProfile, data: Map<string, string | undefined | null>, target: StorageTarget): Promise<void> {
+ // Use current storage service if the profile is same
+ if (this.storageService.isProfileStorageFor(profile)) {
+ return this.writeItems(this.storageService, data, target);
+ }
+
+ const storageDatabase = await this.createStorageDatabase(profile);
+ const storageService = new StorageService(storageDatabase);
+ try {
+ await storageService.initialize();
+ this.writeItems(storageService, data, target);
+ await storageService.flush();
+ } finally {
+ storageService.dispose();
+ await this.closeAndDispose(storageDatabase);
+ }
+ }
+
+ private getItems(storageService: IStorageService): Map<string, IStorageValue> {
+ const result = new Map<string, IStorageValue>();
+ const populate = (target: StorageTarget) => {
+ for (const key of storageService.keys(StorageScope.PROFILE, target)) {
+ result.set(key, { value: storageService.get(key, StorageScope.PROFILE), target });
+ }
+ };
+ populate(StorageTarget.USER);
+ populate(StorageTarget.MACHINE);
+ return result;
+ }
+
+ private writeItems(storageService: IStorageService, items: Map<string, string | undefined | null>, target: StorageTarget): void {
+ for (const [key, value] of items) {
+ storageService.store(key, value, StorageScope.PROFILE, target);
+ }
+ }
+
+ protected async closeAndDispose(storageDatabase: IStorageDatabase): Promise<void> {
+ try {
+ await storageDatabase.close();
+ } finally {
+ if (isDisposable(storageDatabase)) {
+ storageDatabase.dispose();
+ }
+ }
+ }
+
+ protected abstract createStorageDatabase(profile: IUserDataProfile): Promise<IStorageDatabase>;
+}
+
+class StorageService extends AbstractStorageService {
+
+ private readonly profileStorage: IStorage;
+
+ constructor(profileStorageDatabase: IStorageDatabase) {
+ super({ flushInterval: 100, doNotMarkPerf: true });
+ this.profileStorage = this._register(new Storage(profileStorageDatabase));
+ }
+
+ protected doInitialize(): Promise<void> {
+ return this.profileStorage.init();
+ }
+
+ protected getStorage(scope: StorageScope): IStorage | undefined {
+ return scope === StorageScope.PROFILE ? this.profileStorage : undefined;
+ }
+
+ protected getLogDetails(): string | undefined { return undefined; }
+ protected async switchToProfile(): Promise<void> { }
+ protected async switchToWorkspace(): Promise<void> { }
+ isProfileStorageFor() { return false; }
+}
diff --git a/src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts b/src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts
new file mode 100644
index 00000000000..931cc8d69b4
--- /dev/null
+++ b/src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts
@@ -0,0 +1,107 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Emitter, Event } from 'vs/base/common/event';
+import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
+import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
+import { ILogService } from 'vs/platform/log/common/log';
+import { IProfileStorageChanges, IProfileStorageValueChanges } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
+import { loadKeyTargets, StorageScope, TARGET_KEY } from 'vs/platform/storage/common/storage';
+import { IBaseSerializableStorageRequest } from 'vs/platform/storage/common/storageIpc';
+import { IStorageMain } from 'vs/platform/storage/electron-main/storageMain';
+import { IStorageMainService } from 'vs/platform/storage/electron-main/storageMainService';
+import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+export class ProfileStorageChangesListenerChannel extends Disposable implements IServerChannel {
+
+ private readonly _onDidChange: Emitter<IProfileStorageChanges>;
+
+ constructor(
+ private readonly storageMainService: IStorageMainService,
+ private readonly userDataProfilesService: IUserDataProfilesService,
+ private readonly logService: ILogService
+ ) {
+ super();
+ const disposable = this._register(new MutableDisposable<IDisposable>());
+ this._onDidChange = this._register(new Emitter<IProfileStorageChanges>(
+ {
+ // Start listening to profile storage changes only when someone is listening
+ onFirstListenerAdd: () => disposable.value = this.registerStorageChangeListeners(),
+ // Stop listening to profile storage changes when no one is listening
+ onLastListenerRemove: () => disposable.value = undefined
+ }
+ ));
+ }
+
+ private registerStorageChangeListeners(): IDisposable {
+ this.logService.debug('ProfileStorageChangesListenerChannel#registerStorageChangeListeners');
+ const disposables = new DisposableStore();
+ disposables.add(Event.debounce(this.storageMainService.applicationStorage.onDidChangeStorage, (keys: string[] | undefined, e) => {
+ if (keys) {
+ keys.push(e.key);
+ } else {
+ keys = [e.key];
+ }
+ return keys;
+ }, 100)(keys => this.onDidChangeApplicationStorage(keys)));
+ disposables.add(Event.debounce(this.storageMainService.onDidChangeProfileStorageData, (changes: Map<string, { profile: IUserDataProfile; keys: string[]; storage: IStorageMain }> | undefined, e) => {
+ if (!changes) {
+ changes = new Map<string, { profile: IUserDataProfile; keys: string[]; storage: IStorageMain }>();
+ }
+ let profileChanges = changes.get(e.profile.id);
+ if (!profileChanges) {
+ changes.set(e.profile.id, profileChanges = { profile: e.profile, keys: [], storage: e.storage });
+ }
+ profileChanges.keys.push(e.key);
+ return changes;
+ }, 100)(keys => this.onDidChangeProfileStorage(keys)));
+ return disposables;
+ }
+
+ private onDidChangeApplicationStorage(keys: string[]): void {
+ const targetChangedProfiles: IUserDataProfile[] = keys.includes(TARGET_KEY) ? [this.userDataProfilesService.defaultProfile] : [];
+ const profileStorageValueChanges: IProfileStorageValueChanges[] = [];
+ keys = keys.filter(key => key !== TARGET_KEY);
+ if (keys.length) {
+ const keyTargets = loadKeyTargets(this.storageMainService.applicationStorage.storage);
+ profileStorageValueChanges.push({ profile: this.userDataProfilesService.defaultProfile, changes: keys.map(key => ({ key, scope: StorageScope.PROFILE, target: keyTargets[key] })) });
+ }
+ this.triggerEvents(targetChangedProfiles, profileStorageValueChanges);
+ }
+
+ private onDidChangeProfileStorage(changes: Map<string, { profile: IUserDataProfile; keys: string[]; storage: IStorageMain }>): void {
+ const targetChangedProfiles: IUserDataProfile[] = [];
+ const profileStorageValueChanges = new Map<string, IProfileStorageValueChanges>();
+ for (const [profileId, profileChanges] of changes.entries()) {
+ if (profileChanges.keys.includes(TARGET_KEY)) {
+ targetChangedProfiles.push(profileChanges.profile);
+ }
+ const keys = profileChanges.keys.filter(key => key !== TARGET_KEY);
+ if (keys.length) {
+ const keyTargets = loadKeyTargets(profileChanges.storage.storage);
+ profileStorageValueChanges.set(profileId, { profile: profileChanges.profile, changes: keys.map(key => ({ key, scope: StorageScope.PROFILE, target: keyTargets[key] })) });
+ }
+ }
+ this.triggerEvents(targetChangedProfiles, [...profileStorageValueChanges.values()]);
+ }
+
+ private triggerEvents(targetChanges: IUserDataProfile[], valueChanges: IProfileStorageValueChanges[]): void {
+ if (targetChanges.length || valueChanges.length) {
+ this._onDidChange.fire({ valueChanges, targetChanges });
+ }
+ }
+
+ listen(_: unknown, event: string, arg: IBaseSerializableStorageRequest): Event<any> {
+ switch (event) {
+ case 'onDidChange': return this._onDidChange.event;
+ }
+ throw new Error(`Event not found: ${event}`);
+ }
+
+ async call(_: unknown, command: string): Promise<any> {
+ throw new Error(`Call not found: ${command}`);
+ }
+
+}
diff --git a/src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts b/src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts
new file mode 100644
index 00000000000..c668af40dfd
--- /dev/null
+++ b/src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts
@@ -0,0 +1,52 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Emitter, Event } from 'vs/base/common/event';
+import { MutableDisposable } from 'vs/base/common/lifecycle';
+import { IStorageDatabase } from 'vs/base/parts/storage/common/storage';
+import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
+import { ILogService } from 'vs/platform/log/common/log';
+import { AbstractUserDataSyncProfilesStorageService, IProfileStorageChanges, IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
+import { isProfileUsingDefaultStorage, IStorageService } from 'vs/platform/storage/common/storage';
+import { ApplicationStorageDatabaseClient, ProfileStorageDatabaseClient } from 'vs/platform/storage/common/storageIpc';
+import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+export class UserDataSyncProfilesStorageService extends AbstractUserDataSyncProfilesStorageService implements IUserDataSyncProfilesStorageService {
+
+ private readonly _onDidChange: Emitter<IProfileStorageChanges>;
+ readonly onDidChange: Event<IProfileStorageChanges>;
+
+ constructor(
+ @IMainProcessService private readonly mainProcessService: IMainProcessService,
+ @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
+ @IStorageService storageService: IStorageService,
+ @ILogService logService: ILogService,
+ ) {
+ super(storageService);
+
+ const channel = mainProcessService.getChannel('profileStorageListener');
+ const disposable = this._register(new MutableDisposable());
+ this._onDidChange = this._register(new Emitter<IProfileStorageChanges>({
+ // Start listening to profile storage changes only when someone is listening
+ onFirstListenerAdd: () => {
+ disposable.value = channel.listen<IProfileStorageChanges>('onDidChange')(e => {
+ logService.trace('profile storage changes', e);
+ this._onDidChange.fire({
+ targetChanges: e.targetChanges.map(profile => reviveProfile(profile, userDataProfilesService.profilesHome.scheme)),
+ valueChanges: e.valueChanges.map(e => ({ ...e, profile: reviveProfile(e.profile, userDataProfilesService.profilesHome.scheme) }))
+ });
+ });
+ },
+ // Stop listening to profile storage changes when no one is listening
+ onLastListenerRemove: () => disposable.value = undefined
+ }));
+ this.onDidChange = this._onDidChange.event;
+ }
+
+ protected async createStorageDatabase(profile: IUserDataProfile): Promise<IStorageDatabase> {
+ const storageChannel = this.mainProcessService.getChannel('storage');
+ return isProfileUsingDefaultStorage(profile) ? new ApplicationStorageDatabaseClient(storageChannel) : new ProfileStorageDatabaseClient(storageChannel, profile);
+ }
+}
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
index 4bcf4b48db3..c81de3f5134 100644
--- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
@@ -43,8 +43,8 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn
import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { IUserDataProfile, IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { NullPolicyService } from 'vs/platform/policy/common/policy';
-import { IProfileStorageService } from 'vs/platform/storage/common/profileStorageService';
-import { TestProfileStorageService } from 'vs/platform/storage/test/common/profileStorageService.test';
+import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
+import { TestUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test';
export class UserDataSyncClient extends Disposable {
@@ -92,7 +92,7 @@ export class UserDataSyncClient extends Disposable {
const storageService = new TestStorageService(userDataProfilesService.defaultProfile);
this.instantiationService.stub(IStorageService, this._register(storageService));
- this.instantiationService.stub(IProfileStorageService, this._register(new TestProfileStorageService(storageService)));
+ this.instantiationService.stub(IUserDataSyncProfilesStorageService, this._register(new TestUserDataSyncProfilesStorageService(storageService)));
const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService));
await configurationService.initialize();
@@ -307,7 +307,7 @@ class TestStorageService extends InMemoryStorageService {
constructor(private readonly profileStorageProfile: IUserDataProfile) {
super();
}
- override getProfileStorageProfile(): IUserDataProfile | undefined {
- return this.profileStorageProfile;
+ override isProfileStorageFor(profile: IUserDataProfile): boolean {
+ return this.profileStorageProfile.id === profile.id;
}
}
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts
new file mode 100644
index 00000000000..fe4c35019ef
--- /dev/null
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts
@@ -0,0 +1,119 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { Emitter, Event } from 'vs/base/common/event';
+import { DisposableStore } from 'vs/base/common/lifecycle';
+import { URI } from 'vs/base/common/uri';
+import { InMemoryStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage';
+import { AbstractUserDataSyncProfilesStorageService, IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService';
+import { InMemoryStorageService, loadKeyTargets, StorageTarget, TARGET_KEY } from 'vs/platform/storage/common/storage';
+import { IUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
+
+class TestStorageDatabase extends InMemoryStorageDatabase {
+
+ private readonly _onDidChangeItemsExternal = new Emitter<IStorageItemsChangeEvent>();
+ override readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event;
+
+ override async updateItems(request: IUpdateRequest): Promise<void> {
+ await super.updateItems(request);
+ if (request.insert || request.delete) {
+ this._onDidChangeItemsExternal.fire({ changed: request.insert, deleted: request.delete });
+ }
+ }
+}
+
+export class TestUserDataSyncProfilesStorageService extends AbstractUserDataSyncProfilesStorageService implements IUserDataSyncProfilesStorageService {
+
+ readonly onDidChange = Event.None;
+ private databases = new Map<string, InMemoryStorageDatabase>();
+
+ async createStorageDatabase(profile: IUserDataProfile): Promise<InMemoryStorageDatabase> {
+ let database = this.databases.get(profile.id);
+ if (!database) {
+ this.databases.set(profile.id, database = new TestStorageDatabase());
+ }
+ return database;
+ }
+
+ protected override async closeAndDispose(): Promise<void> { }
+}
+
+suite('ProfileStorageService', () => {
+
+ const disposables = new DisposableStore();
+ const profile = toUserDataProfile('test', URI.file('foo'));
+ let testObject: TestUserDataSyncProfilesStorageService;
+ let storage: Storage;
+
+ setup(async () => {
+ testObject = disposables.add(new TestUserDataSyncProfilesStorageService(new InMemoryStorageService()));
+ storage = new Storage(await testObject.createStorageDatabase(profile));
+ await storage.init();
+ });
+
+ teardown(() => disposables.clear());
+
+ test('read empty storage', async () => {
+ const actual = await testObject.readStorageData(profile);
+
+ assert.strictEqual(actual.size, 0);
+ });
+
+ test('read storage with data', async () => {
+ storage.set('foo', 'bar');
+ storage.set(TARGET_KEY, JSON.stringify({ foo: StorageTarget.USER }));
+ await storage.flush();
+
+ const actual = await testObject.readStorageData(profile);
+
+ assert.strictEqual(actual.size, 1);
+ assert.deepStrictEqual(actual.get('foo'), { 'value': 'bar', 'target': StorageTarget.USER });
+ });
+
+ test('write in empty storage', async () => {
+ const data = new Map<string, string>();
+ data.set('foo', 'bar');
+ await testObject.updateStorageData(profile, data, StorageTarget.USER);
+
+ assert.strictEqual(storage.items.size, 2);
+ assert.deepStrictEqual(loadKeyTargets(storage), { foo: StorageTarget.USER });
+ assert.strictEqual(storage.get('foo'), 'bar');
+ });
+
+ test('write in storage with data', async () => {
+ storage.set('foo', 'bar');
+ storage.set(TARGET_KEY, JSON.stringify({ foo: StorageTarget.USER }));
+ await storage.flush();
+
+ const data = new Map<string, string>();
+ data.set('abc', 'xyz');
+ await testObject.updateStorageData(profile, data, StorageTarget.MACHINE);
+
+ assert.strictEqual(storage.items.size, 3);
+ assert.deepStrictEqual(loadKeyTargets(storage), { foo: StorageTarget.USER, abc: StorageTarget.MACHINE });
+ assert.strictEqual(storage.get('foo'), 'bar');
+ assert.strictEqual(storage.get('abc'), 'xyz');
+ });
+
+ test('write in storage with data (insert, update, remove)', async () => {
+ storage.set('foo', 'bar');
+ storage.set('abc', 'xyz');
+ storage.set(TARGET_KEY, JSON.stringify({ foo: StorageTarget.USER, abc: StorageTarget.MACHINE }));
+ await storage.flush();
+
+ const data = new Map<string, string | undefined>();
+ data.set('foo', undefined);
+ data.set('abc', 'def');
+ data.set('var', 'const');
+ await testObject.updateStorageData(profile, data, StorageTarget.USER);
+
+ assert.strictEqual(storage.items.size, 3);
+ assert.deepStrictEqual(loadKeyTargets(storage), { abc: StorageTarget.USER, var: StorageTarget.USER });
+ assert.strictEqual(storage.get('abc'), 'def');
+ assert.strictEqual(storage.get('var'), 'const');
+ });
+
+});