mirror of
https://github.com/coder/code-server.git
synced 2026-05-28 16:09:35 +00:00
Update to VS Code 1.52.1
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export interface IExtensionIdWithVersion {
|
||||
id: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const IExtensionsStorageSyncService = createDecorator<IExtensionsStorageSyncService>('IExtensionsStorageSyncService');
|
||||
|
||||
export interface IExtensionsStorageSyncService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onDidChangeExtensionsStorage: Event<void>;
|
||||
setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void;
|
||||
getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined;
|
||||
|
||||
}
|
||||
|
||||
const EXTENSION_KEYS_ID_VERSION_REGEX = /^extensionKeys\/([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
|
||||
|
||||
export class ExtensionsStorageSyncService extends Disposable implements IExtensionsStorageSyncService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private static toKey(extension: IExtensionIdWithVersion): string {
|
||||
return `extensionKeys/${extension.id}@${extension.version}`;
|
||||
}
|
||||
|
||||
private static fromKey(key: string): IExtensionIdWithVersion | undefined {
|
||||
const matches = EXTENSION_KEYS_ID_VERSION_REGEX.exec(key);
|
||||
if (matches && matches[1]) {
|
||||
return { id: matches[1], version: matches[2] };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private readonly _onDidChangeExtensionsStorage = this._register(new Emitter<void>());
|
||||
readonly onDidChangeExtensionsStorage = this._onDidChangeExtensionsStorage.event;
|
||||
|
||||
private readonly extensionsWithKeysForSync = new Set<string>();
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this.initialize();
|
||||
this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorageValue(e)));
|
||||
}
|
||||
|
||||
private initialize(): void {
|
||||
const keys = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
for (const key of keys) {
|
||||
const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(key);
|
||||
if (extensionIdWithVersion) {
|
||||
this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeStorageValue(e: IStorageValueChangeEvent): void {
|
||||
if (e.scope !== StorageScope.GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// State of extension with keys for sync has changed
|
||||
if (this.extensionsWithKeysForSync.has(e.key.toLowerCase())) {
|
||||
this._onDidChangeExtensionsStorage.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
// Keys for sync of an extension has changed
|
||||
const extensionIdWithVersion = ExtensionsStorageSyncService.fromKey(e.key);
|
||||
if (extensionIdWithVersion) {
|
||||
this.extensionsWithKeysForSync.add(extensionIdWithVersion.id.toLowerCase());
|
||||
this._onDidChangeExtensionsStorage.fire();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this.storageService.store(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), JSON.stringify(keys), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
getKeysForSync(extensionIdWithVersion: IExtensionIdWithVersion): string[] | undefined {
|
||||
const keysForSyncValue = this.storageService.get(ExtensionsStorageSyncService.toKey(extensionIdWithVersion), StorageScope.GLOBAL);
|
||||
return keysForSyncValue ? JSON.parse(keysForSyncValue) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,12 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { forEach, IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
|
||||
interface IExtensionResourceMergeResult extends IAcceptResult {
|
||||
readonly added: ISyncExtension[];
|
||||
@@ -105,7 +105,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IExtensionsStorageSyncService private readonly extensionsStorageSyncService: IExtensionsStorageSyncService,
|
||||
) {
|
||||
super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(
|
||||
@@ -114,16 +114,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)),
|
||||
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)),
|
||||
this.extensionEnablementService.onDidChangeEnablement,
|
||||
this.storageKeysSyncRegistryService.onDidChangeExtensionStorageKeys,
|
||||
Event.filter(this.storageService.onDidChangeStorage, e => e.scope === StorageScope.GLOBAL
|
||||
&& this.storageKeysSyncRegistryService.extensionsStorageKeys.some(([extensionIdentifier]) => areSameExtensions(extensionIdentifier, { id: e.key })))),
|
||||
this.extensionsStorageSyncService.onDidChangeExtensionsStorage),
|
||||
() => undefined, 500)(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData?.skippedExtensions || [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData?.syncData ? await parseAndMigrateExtensions(lastSyncUserData.syncData, this.extensionManagementService) : null;
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
@@ -352,7 +350,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r)));
|
||||
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove);
|
||||
await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
|
||||
removeFromSkipped.push(extensionToRemove.identifier);
|
||||
}));
|
||||
@@ -365,9 +363,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// Builtin Extension Sync: Enablement & State
|
||||
if (installedExtension && installedExtension.isBuiltin) {
|
||||
if (e.state && installedExtension.manifest.version === e.version) {
|
||||
const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(e.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version);
|
||||
}
|
||||
if (e.disabled) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
|
||||
@@ -393,9 +389,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
(installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */
|
||||
: !!extension /* Installable */)
|
||||
) {
|
||||
const extensionState = JSON.parse(this.storageService.get(e.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(e.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(e.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version);
|
||||
}
|
||||
|
||||
if (extension) {
|
||||
@@ -413,7 +407,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
// Install only if the extension does not exist
|
||||
if (!installedExtension) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
|
||||
removeFromSkipped.push(extension.identifier);
|
||||
}
|
||||
@@ -442,6 +436,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
return newSkippedExtensions;
|
||||
}
|
||||
|
||||
private updateExtensionState(state: IStringDictionary<any>, id: string, version?: string): void {
|
||||
const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}');
|
||||
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined;
|
||||
if (keys) {
|
||||
keys.forEach(key => extensionState[key] = state[key]);
|
||||
} else {
|
||||
forEach(state, ({ key, value }) => extensionState[key] = value);
|
||||
}
|
||||
this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
@@ -457,10 +462,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
if (!isBuiltin) {
|
||||
syncExntesion.installed = true;
|
||||
}
|
||||
const keys = this.storageKeysSyncRegistryService.getExtensioStorageKeys({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
|
||||
try {
|
||||
try {
|
||||
const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version });
|
||||
if (keys) {
|
||||
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
|
||||
const extensionStorageState = JSON.parse(extensionStorageValue);
|
||||
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
|
||||
if (keys.includes(key)) {
|
||||
@@ -468,9 +473,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
||||
}
|
||||
return state;
|
||||
}, {});
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
|
||||
}
|
||||
return syncExntesion;
|
||||
});
|
||||
@@ -499,6 +504,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.initializeRemoteExtensions(remoteExtensions);
|
||||
}
|
||||
|
||||
protected async initializeRemoteExtensions(remoteExtensions: ISyncExtension[]): Promise<ILocalExtension[]> {
|
||||
const newlyEnabledExtensions: ILocalExtension[] = [];
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const newExtensionsToSync = new Map<string, ISyncExtension>();
|
||||
const installedExtensionsToSync: ISyncExtension[] = [];
|
||||
@@ -528,10 +538,13 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
try {
|
||||
const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!;
|
||||
if (extensionToSync.state) {
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
|
||||
if (!toDisable.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) {
|
||||
newlyEnabledExtensions.push(local);
|
||||
}
|
||||
this.logService.info(`Installed extension.`, galleryExtension.identifier.id);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
@@ -551,11 +564,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
|
||||
if (extensionToSync.state) {
|
||||
const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}');
|
||||
forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL);
|
||||
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
return newlyEnabledExtensions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,24 +6,22 @@
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export interface IMergeResult {
|
||||
local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
remote: IStringDictionary<IStorageValue> | null;
|
||||
skipped: string[];
|
||||
}
|
||||
|
||||
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: ReadonlyArray<IStorageKey>, previouslySkipped: string[], logService: ILogService): IMergeResult {
|
||||
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: { machine: ReadonlyArray<string>, unregistered: ReadonlyArray<string> }, logService: ILogService): IMergeResult {
|
||||
if (!remoteStorage) {
|
||||
return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} }, skipped: [] };
|
||||
return { remote: Object.keys(localStorage).length > 0 ? localStorage : null, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const localToRemote = compare(localStorage, remoteStorage);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { remote: null, local: { added: {}, removed: [], updated: {} }, skipped: [] };
|
||||
return { remote: null, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
@@ -31,26 +29,48 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
|
||||
const local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> } = { added: {}, removed: [], updated: {} };
|
||||
const remote: IStringDictionary<IStorageValue> = objects.deepClone(remoteStorage);
|
||||
const skipped: string[] = [];
|
||||
|
||||
// Added in local
|
||||
for (const key of baseToLocal.added.values()) {
|
||||
// Skip if local was not synced before and remote also has the key
|
||||
// In this case, remote gets precedence
|
||||
if (!baseStorage && baseToRemote.added.has(key)) {
|
||||
continue;
|
||||
} else {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updated in local
|
||||
for (const key of baseToLocal.updated.values()) {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
|
||||
// Removed in local
|
||||
for (const key of baseToLocal.removed.values()) {
|
||||
// Do not remove from remote if key is not registered.
|
||||
if (storageKeys.unregistered.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
delete remote[key];
|
||||
}
|
||||
|
||||
// Added in remote
|
||||
for (const key of baseToRemote.added.values()) {
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped adding ${key} in local storage as it is not registered.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.info(`GlobalState: Skipped adding ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped adding ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
// Skip if the value is also added in local from the time it is last synced
|
||||
if (baseStorage && baseToLocal.added.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
if (localValue && localValue.value === remoteValue.value) {
|
||||
continue;
|
||||
}
|
||||
if (baseToLocal.added.has(key)) {
|
||||
if (localValue) {
|
||||
local.updated[key] = remoteValue;
|
||||
} else {
|
||||
local.added[key] = remoteValue;
|
||||
@@ -60,14 +80,12 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
// Updated in Remote
|
||||
for (const key of baseToRemote.updated.values()) {
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped updating ${key} in local storage as is not registered.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in local storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
// Skip if the value is also updated or removed in local
|
||||
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
@@ -79,67 +97,18 @@ export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStor
|
||||
|
||||
// Removed in remote
|
||||
for (const key of baseToRemote.removed.values()) {
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in local storage. It is not registered to sync.`);
|
||||
if (storageKeys.machine.includes(key)) {
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in local storage because it is declared as machine scoped.`);
|
||||
continue;
|
||||
}
|
||||
// Skip if the value is also updated or removed in local
|
||||
if (baseToLocal.updated.has(key) || baseToLocal.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
local.removed.push(key);
|
||||
}
|
||||
|
||||
// Added in local
|
||||
for (const key of baseToLocal.added.values()) {
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updated in local
|
||||
for (const key of baseToLocal.updated.values()) {
|
||||
if (baseToRemote.updated.has(key) || baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const remoteValue = remote[key];
|
||||
const localValue = localStorage[key];
|
||||
if (localValue.version < remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${localValue.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
remote[key] = localValue;
|
||||
}
|
||||
|
||||
// Removed in local
|
||||
for (const key of baseToLocal.removed.values()) {
|
||||
// do not remove from remote if it is updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
// do not remove from remote if storage key is not found
|
||||
if (!storageKey) {
|
||||
skipped.push(key);
|
||||
logService.trace(`GlobalState: Skipped removing ${key} in remote storage. It is not registered to sync.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const remoteValue = remote[key];
|
||||
// do not remove from remote if local data version is old
|
||||
if (storageKey.version < remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in remote storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// add to local if it was skipped before
|
||||
if (previouslySkipped.indexOf(key) !== -1) {
|
||||
local.added[key] = remote[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
delete remote[key];
|
||||
}
|
||||
|
||||
return { local, remote: areSame(remote, remoteStorage) ? null : remote, skipped };
|
||||
return { local, remote: areSame(remote, remoteStorage) ? null : remote };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
|
||||
@@ -21,29 +21,34 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const argvStoragePrefx = 'globalState.argv.';
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
type StorageKeys = { machine: string[], user: string[], unregistered: string[] };
|
||||
|
||||
interface IGlobalStateResourceMergeResult extends IAcceptResult {
|
||||
readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
readonly remote: IStringDictionary<IStorageValue> | null;
|
||||
}
|
||||
|
||||
export interface IGlobalStateResourcePreview extends IResourcePreview {
|
||||
readonly skippedStorageKeys: string[];
|
||||
readonly localUserData: IGlobalState;
|
||||
readonly previewResult: IGlobalStateResourceMergeResult;
|
||||
readonly storageKeys: StorageKeys;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
skippedStorageKeys: string[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronises global state that includes
|
||||
* - Global storage with user scope
|
||||
* - Locale from argv properties
|
||||
*
|
||||
* Global storage is synced without checking version just like other resources (settings, keybindings).
|
||||
* If there is a change in format of the value of a storage key which requires migration then
|
||||
* Owner of that key should remove that key from user scope and replace that with new user scoped key.
|
||||
*/
|
||||
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
private static readonly GLOBAL_STATE_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'globalState', path: `/globalState.json` });
|
||||
@@ -63,23 +68,22 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(SyncResource.GlobalState, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(this.fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
|
||||
this._register(fileService.watch(this.extUri.dirname(this.environmentService.argvResource)));
|
||||
this._register(
|
||||
Event.any(
|
||||
/* Locale change */
|
||||
Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
|
||||
/* Storage change */
|
||||
Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key)),
|
||||
/* Storage key registered */
|
||||
this.storageKeysSyncRegistryService.onDidChangeStorageKeys
|
||||
Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)),
|
||||
/* Global storage with user target has changed */
|
||||
Event.filter(storageService.onDidChangeValue, e => e.scope === StorageScope.GLOBAL && e.target !== undefined ? e.target === StorageTarget.USER : storageService.keys(StorageScope.GLOBAL, StorageTarget.USER).includes(e.key)),
|
||||
/* Storage key target has changed */
|
||||
this.storageService.onDidChangeTarget
|
||||
)((() => this.triggerLocalChange()))
|
||||
);
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IGlobalStateResourcePreview[]> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState: IGlobalState | null = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
|
||||
@@ -91,7 +95,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
|
||||
}
|
||||
|
||||
const { local, remote, skipped } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
|
||||
const storageKeys = this.getStorageKeys(lastSyncGlobalState);
|
||||
const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, storageKeys, this.logService);
|
||||
const previewResult: IGlobalStateResourceMergeResult = {
|
||||
content: null,
|
||||
local,
|
||||
@@ -101,7 +106,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
};
|
||||
|
||||
return [{
|
||||
skippedStorageKeys: skipped,
|
||||
localResource: this.localResource,
|
||||
localContent: this.format(localGloablState),
|
||||
localUserData: localGloablState,
|
||||
@@ -112,6 +116,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
localChange: previewResult.localChange,
|
||||
remoteChange: previewResult.remoteChange,
|
||||
acceptedResource: this.acceptedResource,
|
||||
storageKeys
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -152,7 +157,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
private async acceptRemote(resourcePreview: IGlobalStateResourcePreview): Promise<IGlobalStateResourceMergeResult> {
|
||||
if (resourcePreview.remoteContent !== null) {
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(resourcePreview.remoteContent);
|
||||
const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), resourcePreview.skippedStorageKeys, this.logService);
|
||||
const { local, remote } = merge(resourcePreview.localUserData.storage, remoteGlobalState.storage, null, resourcePreview.storageKeys, this.logService);
|
||||
return {
|
||||
content: resourcePreview.remoteContent,
|
||||
local,
|
||||
@@ -171,8 +176,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { localUserData, skippedStorageKeys } = resourcePreviews[0][0];
|
||||
protected async applyResult(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: [IGlobalStateResourcePreview, IGlobalStateResourceMergeResult][], force: boolean): Promise<void> {
|
||||
let { localUserData } = resourcePreviews[0][0];
|
||||
let { local, remote, localChange, remoteChange } = resourcePreviews[0][1];
|
||||
|
||||
if (localChange === Change.None && remoteChange === Change.None) {
|
||||
@@ -195,10 +200,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`);
|
||||
}
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref || !equals(lastSyncUserData.skippedStorageKeys, skippedStorageKeys)) {
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
// update last sync
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized ui state...`);
|
||||
await this.updateLastSyncUserData(remoteUserData, { skippedStorageKeys });
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized ui state`);
|
||||
}
|
||||
}
|
||||
@@ -267,10 +272,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
|
||||
}
|
||||
}
|
||||
for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) {
|
||||
for (const key of this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER)) {
|
||||
const value = this.storageService.get(key, StorageScope.GLOBAL);
|
||||
if (value) {
|
||||
storage[key] = { version, value };
|
||||
storage[key] = { version: 1, value };
|
||||
}
|
||||
}
|
||||
return { storage };
|
||||
@@ -317,7 +322,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
if (updatedStorageKeys.length) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`);
|
||||
for (const key of Object.keys(updatedStorage)) {
|
||||
this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL);
|
||||
this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage));
|
||||
}
|
||||
@@ -336,8 +341,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
}
|
||||
|
||||
private getSyncStorageKeys(): IStorageKey[] {
|
||||
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
|
||||
private getStorageKeys(lastSyncGlobalState: IGlobalState | null): StorageKeys {
|
||||
const user = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.USER);
|
||||
const machine = this.storageService.keys(StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
const registered = [...user, ...machine];
|
||||
const unregistered = lastSyncGlobalState?.storage ? Object.keys(lastSyncGlobalState.storage).filter(key => !key.startsWith(argvStoragePrefx) && !registered.includes(key) && this.storageService.get(key, StorageScope.GLOBAL) !== undefined) : [];
|
||||
return { user, machine, unregistered };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +394,7 @@ export class GlobalStateInitializer extends AbstractInitializer {
|
||||
|
||||
if (Object.keys(storage).length) {
|
||||
for (const key of Object.keys(storage)) {
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL);
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -35,6 +36,10 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview {
|
||||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
interface ILastSyncUserData extends IRemoteUserData {
|
||||
platformSpecific?: boolean;
|
||||
}
|
||||
|
||||
export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (!platformSpecific) {
|
||||
@@ -72,11 +77,12 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(environmentService.keybindingsResource, SyncResource.Keybindings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
|
||||
this._register(Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('settingsSync.keybindingsPerPlatform'))(() => this.triggerLocalChange()));
|
||||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IKeybindingsResourcePreview[]> {
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null, token: CancellationToken): Promise<IKeybindingsResourcePreview[]> {
|
||||
const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
const lastSyncContent: string | null = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
|
||||
const lastSyncContent: string | null = lastSyncUserData ? this.getKeybindingsContentFromLastSyncUserData(lastSyncUserData) : null;
|
||||
|
||||
// Get file content last to get the latest
|
||||
const fileContent = await this.getLocalFileContent();
|
||||
@@ -204,7 +210,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
if (localChange !== Change.None) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
|
||||
if (fileContent) {
|
||||
await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null));
|
||||
await this.backupLocal(this.toSyncContent(fileContent.value.toString()));
|
||||
}
|
||||
await this.updateLocalFileContent(content || '[]', fileContent, force);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
|
||||
@@ -212,7 +218,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (remoteChange !== Change.None) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating remote keybindings...`);
|
||||
const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData ? remoteUserData.syncData.content : null);
|
||||
const remoteContents = this.toSyncContent(content || '[]', remoteUserData.syncData?.content);
|
||||
remoteUserData = await this.updateRemoteUserData(remoteContents, force ? null : remoteUserData.ref);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated remote keybindings`);
|
||||
}
|
||||
@@ -224,15 +230,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
|
||||
if (lastSyncUserData?.ref !== remoteUserData.ref) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating last synchronized keybindings...`);
|
||||
const lastSyncContent = content !== null ? this.toSyncContent(content, null) : remoteUserData.syncData?.content;
|
||||
await this.updateLastSyncUserData({
|
||||
ref: remoteUserData.ref,
|
||||
syncData: lastSyncContent ? {
|
||||
version: remoteUserData.syncData ? remoteUserData.syncData.version : this.version,
|
||||
machineId: remoteUserData.syncData!.machineId,
|
||||
content: lastSyncContent
|
||||
} : null
|
||||
});
|
||||
await this.updateLastSyncUserData(remoteUserData, { platformSpecific: this.syncKeybindingsPerPlatform() });
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated last synchronized keybindings`);
|
||||
}
|
||||
|
||||
@@ -281,6 +279,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
return null;
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromLastSyncUserData(lastSyncUserData: ILastSyncUserData): string | null {
|
||||
if (!lastSyncUserData.syncData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return null if there is a change in platform specific property from last time sync.
|
||||
if (lastSyncUserData.platformSpecific !== undefined && lastSyncUserData.platformSpecific !== this.syncKeybindingsPerPlatform()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content);
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform());
|
||||
@@ -290,7 +301,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
||||
}
|
||||
}
|
||||
|
||||
private toSyncContent(keybindingsContent: string, syncContent: string | null): string {
|
||||
private toSyncContent(keybindingsContent: string, syncContent?: string): string {
|
||||
let parsed: ISyncContent = {};
|
||||
try {
|
||||
parsed = JSON.parse(syncContent || '{}');
|
||||
|
||||
@@ -13,7 +13,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
@@ -198,6 +198,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
||||
await this.backupLocal(JSON.stringify(this.toSettingsSyncContent(fileContent.value.toString())));
|
||||
}
|
||||
await this.updateLocalFileContent(content, fileContent, force);
|
||||
await this.configurationService.reloadConfiguration(ConfigurationTarget.USER_LOCAL);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local settings`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +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 { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IStorageKey {
|
||||
|
||||
readonly key: string;
|
||||
readonly version: number;
|
||||
|
||||
}
|
||||
|
||||
export interface IExtensionIdWithVersion {
|
||||
id: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export namespace ExtensionIdWithVersion {
|
||||
|
||||
const EXTENSION_ID_VERSION_REGEX = /^([^.]+\..+)@(\d+\.\d+\.\d+(-.*)?)$/;
|
||||
|
||||
export function toKey(extension: IExtensionIdWithVersion): string {
|
||||
return `${extension.id}@${extension.version}`;
|
||||
}
|
||||
|
||||
export function fromKey(key: string): IExtensionIdWithVersion | undefined {
|
||||
const matches = EXTENSION_ID_VERSION_REGEX.exec(key);
|
||||
if (matches && matches[1]) {
|
||||
return { id: matches[1], version: matches[2] };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const IStorageKeysSyncRegistryService = createDecorator<IStorageKeysSyncRegistryService>('IStorageKeysSyncRegistryService');
|
||||
|
||||
export interface IStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* All registered storage keys
|
||||
*/
|
||||
readonly storageKeys: ReadonlyArray<IStorageKey>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when storage keys are changed
|
||||
*/
|
||||
readonly onDidChangeStorageKeys: Event<ReadonlyArray<IStorageKey>>;
|
||||
|
||||
/**
|
||||
* Register a storage key that has to be synchronized during sync.
|
||||
*/
|
||||
registerStorageKey(key: IStorageKey): void;
|
||||
|
||||
/**
|
||||
* All registered extensions storage keys
|
||||
*/
|
||||
readonly extensionsStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray<string>]>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when extension storage keys are changed
|
||||
*/
|
||||
onDidChangeExtensionStorageKeys: Event<[IExtensionIdWithVersion, ReadonlyArray<string>]>;
|
||||
|
||||
/**
|
||||
* Register storage keys that has to be synchronized for the given extension.
|
||||
*/
|
||||
registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void;
|
||||
|
||||
/**
|
||||
* Returns storage keys of the given extension that has to be synchronized.
|
||||
*/
|
||||
getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray<string> | undefined;
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
protected readonly _storageKeys = new Map<string, IStorageKey>();
|
||||
get storageKeys(): ReadonlyArray<IStorageKey> { return [...this._storageKeys.values()]; }
|
||||
|
||||
protected readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
|
||||
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
|
||||
|
||||
protected readonly _extensionsStorageKeys = new Map<string, string[]>();
|
||||
get extensionsStorageKeys() {
|
||||
const result: [IExtensionIdWithVersion, ReadonlyArray<string>][] = [];
|
||||
this._extensionsStorageKeys.forEach((keys, extension) => result.push([ExtensionIdWithVersion.fromKey(extension)!, keys]));
|
||||
return result;
|
||||
}
|
||||
protected readonly _onDidChangeExtensionStorageKeys = this._register(new Emitter<[IExtensionIdWithVersion, ReadonlyArray<string>]>());
|
||||
readonly onDidChangeExtensionStorageKeys = this._onDidChangeExtensionStorageKeys.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._register(toDisposable(() => this._storageKeys.clear()));
|
||||
}
|
||||
|
||||
getExtensioStorageKeys(extension: IExtensionIdWithVersion): ReadonlyArray<string> | undefined {
|
||||
return this._extensionsStorageKeys.get(ExtensionIdWithVersion.toKey(extension));
|
||||
}
|
||||
|
||||
protected updateExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this._extensionsStorageKeys.set(ExtensionIdWithVersion.toKey(extension), keys);
|
||||
this._onDidChangeExtensionStorageKeys.fire([extension, keys]);
|
||||
}
|
||||
|
||||
abstract registerStorageKey(key: IStorageKey): void;
|
||||
abstract registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void;
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryService extends AbstractStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
if (!this._storageKeys.has(storageKey.key)) {
|
||||
this._storageKeys.set(storageKey.key, storageKey);
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
}
|
||||
|
||||
registerExtensionStorageKeys(extension: IExtensionIdWithVersion, keys: string[]): void {
|
||||
this.updateExtensionStorageKeys(extension, keys);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -55,7 +55,7 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU
|
||||
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
isEnabled(defaultEnablement?: boolean): boolean {
|
||||
@@ -73,15 +73,15 @@ export class UserDataAutoSyncEnablementService extends Disposable implements _IU
|
||||
}
|
||||
|
||||
setEnablement(enabled: boolean): void {
|
||||
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
|
||||
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope !== StorageScope.GLOBAL) {
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope !== StorageScope.GLOBAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enablementKey === workspaceStorageChangeEvent.key) {
|
||||
if (enablementKey === storageChangeEvent.key) {
|
||||
this._onDidChangeEnablement.fire(this.isEnabled());
|
||||
return;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
private set syncUrl(syncUrl: URI | undefined) {
|
||||
if (syncUrl) {
|
||||
this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL);
|
||||
this.storageService.store(storeUrlKey, syncUrl.toString(), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.storageService.remove(storeUrlKey, StorageScope.GLOBAL);
|
||||
}
|
||||
@@ -325,7 +325,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
|
||||
}
|
||||
|
||||
private async disableMachineEventually(): Promise<void> {
|
||||
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL);
|
||||
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
await timeout(1000 * 60 * 10);
|
||||
|
||||
// Return if got stopped meanwhile.
|
||||
@@ -526,7 +526,7 @@ class AutoSync extends Disposable {
|
||||
|
||||
// Update local session id
|
||||
if (manifest && manifest.session !== sessionId) {
|
||||
this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL);
|
||||
this.storageService.store(sessionIdKey, manifest.session, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
// Return if cancellation is requested
|
||||
|
||||
@@ -220,7 +220,9 @@ export enum UserDataSyncErrorCode {
|
||||
TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */
|
||||
|
||||
// Local Errors
|
||||
ConnectionRefused = 'ConnectionRefused',
|
||||
RequestFailed = 'RequestFailed',
|
||||
RequestCanceled = 'RequestCanceled',
|
||||
RequestTimeout = 'RequestTimeout',
|
||||
NoRef = 'NoRef',
|
||||
TurnedOff = 'TurnedOff',
|
||||
SessionExpired = 'SessionExpired',
|
||||
@@ -252,7 +254,7 @@ export class UserDataSyncError extends Error {
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreError extends UserDataSyncError {
|
||||
constructor(message: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) {
|
||||
constructor(message: string, readonly url: string, code: UserDataSyncErrorCode, readonly operationId: string | undefined) {
|
||||
super(message, code, undefined, operationId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncServic
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey, IExtensionIdWithVersion, AbstractStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { IExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
||||
export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
@@ -175,68 +173,6 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
}
|
||||
|
||||
type StorageKeysSyncRegistryServiceInitData = { storageKeys: ReadonlyArray<IStorageKey>, extensionsStorageKeys: ReadonlyArray<[IExtensionIdWithVersion, ReadonlyArray<string>]> };
|
||||
|
||||
export class StorageKeysSyncRegistryChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IStorageKeysSyncRegistryService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys;
|
||||
case 'onDidChangeExtensionStorageKeys': return this.service.onDidChangeExtensionStorageKeys;
|
||||
}
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case '_getInitialData': return Promise.resolve<StorageKeysSyncRegistryServiceInitData>({ storageKeys: this.service.storageKeys, extensionsStorageKeys: this.service.extensionsStorageKeys });
|
||||
case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0]));
|
||||
case 'registerExtensionStorageKeys': return Promise.resolve(this.service.registerExtensionStorageKeys(args[0], args[1]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryChannelClient extends AbstractStorageKeysSyncRegistryService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
super();
|
||||
this.channel.call<StorageKeysSyncRegistryServiceInitData>('_getInitialData').then(({ storageKeys, extensionsStorageKeys }) => {
|
||||
this.updateStorageKeys(storageKeys);
|
||||
this.updateExtensionsStorageKeys(extensionsStorageKeys);
|
||||
this._register(this.channel.listen<ReadonlyArray<IStorageKey>>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys)));
|
||||
this._register(this.channel.listen<[IExtensionIdentifierWithVersion, string[]]>('onDidChangeExtensionStorageKeys')(e => this.updateExtensionStorageKeys(e[0], e[1])));
|
||||
});
|
||||
}
|
||||
|
||||
private async updateStorageKeys(storageKeys: ReadonlyArray<IStorageKey>): Promise<void> {
|
||||
this._storageKeys.clear();
|
||||
for (const storageKey of storageKeys) {
|
||||
this._storageKeys.set(storageKey.key, storageKey);
|
||||
}
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
|
||||
private async updateExtensionsStorageKeys(extensionStorageKeys: ReadonlyArray<[IExtensionIdentifierWithVersion, ReadonlyArray<string>]>): Promise<void> {
|
||||
for (const [extension, keys] of extensionStorageKeys) {
|
||||
this.updateExtensionStorageKeys(extension, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
this.channel.call('registerStorageKey', [storageKey]);
|
||||
}
|
||||
|
||||
registerExtensionStorageKeys(extension: IExtensionIdentifierWithVersion, keys: string[]): void {
|
||||
this.channel.call('registerExtensionStorageKeys', [extension, keys]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncMachinesServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncMachinesService) { }
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -103,7 +103,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData
|
||||
machine.name = name;
|
||||
await this.writeMachinesData(machineData);
|
||||
if (machineData.machines.some(({ id }) => id === currentMachineId)) {
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL);
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type SyncEnablementClassification = {
|
||||
@@ -28,7 +28,7 @@ export class UserDataSyncResourceEnablementService extends Disposable implements
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
isResourceEnabled(resource: SyncResource): boolean {
|
||||
@@ -39,13 +39,13 @@ export class UserDataSyncResourceEnablementService extends Disposable implements
|
||||
if (this.isResourceEnabled(resource) !== enabled) {
|
||||
const resourceEnablementKey = getEnablementKey(resource);
|
||||
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled });
|
||||
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL);
|
||||
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === workspaceStorageChangeEvent.key)[0];
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
const resourceKey = ALL_SYNC_RESOURCES.filter(resourceKey => getEnablementKey(resourceKey) === storageChangeEvent.key)[0];
|
||||
if (resourceKey) {
|
||||
this._onDidChangeResourceEnablement.fire([resourceKey, this.isResourceEnabled(resourceKey)]);
|
||||
return;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import {
|
||||
IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode,
|
||||
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change, IUserDataSyncStoreManagementService
|
||||
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change, IUserDataSyncStoreManagementService, UserDataSyncStoreError
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -16,7 +16,7 @@ import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalS
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
@@ -30,6 +30,7 @@ import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
type SyncErrorClassification = {
|
||||
code: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
service: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
url?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
executionId?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
@@ -118,9 +119,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
}
|
||||
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
|
||||
} catch (error) {
|
||||
error = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() });
|
||||
throw error;
|
||||
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.reportUserDataSyncError(userDataSyncError, executionId);
|
||||
throw userDataSyncError;
|
||||
}
|
||||
|
||||
let executed = false;
|
||||
@@ -156,9 +157,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
try {
|
||||
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
|
||||
} catch (error) {
|
||||
error = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() });
|
||||
throw error;
|
||||
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.reportUserDataSyncError(userDataSyncError, executionId);
|
||||
throw userDataSyncError;
|
||||
}
|
||||
|
||||
return new ManualSyncTask(executionId, manifest, syncHeaders, this.synchronisers, this.logService);
|
||||
@@ -202,9 +203,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
|
||||
this.updateLastSyncTime();
|
||||
} catch (error) {
|
||||
error = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.telemetryService.publicLog2<{ code: string, service: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: error.code, resource: error.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() });
|
||||
throw error;
|
||||
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
|
||||
this.reportUserDataSyncError(userDataSyncError, executionId);
|
||||
throw userDataSyncError;
|
||||
} finally {
|
||||
this.updateStatus();
|
||||
this._onSyncErrors.fire(this._syncErrors);
|
||||
@@ -365,7 +366,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
private updateLastSyncTime(): void {
|
||||
if (this.status === SyncStatus.Idle) {
|
||||
this._lastSyncTime = new Date().getTime();
|
||||
this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL);
|
||||
this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
this._onDidChangeLastSyncTime.fire(this._lastSyncTime);
|
||||
}
|
||||
}
|
||||
@@ -390,6 +391,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
this.logService.error(`${source}: ${toErrorMessage(e)}`);
|
||||
}
|
||||
|
||||
private reportUserDataSyncError(userDataSyncError: UserDataSyncError, executionId: string) {
|
||||
this.telemetryService.publicLog2<{ code: string, service: string, url?: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error',
|
||||
{ code: userDataSyncError.code, url: userDataSyncError instanceof UserDataSyncStoreError ? userDataSyncError.url : undefined, resource: userDataSyncError.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() });
|
||||
}
|
||||
|
||||
private computeConflicts(): [SyncResource, IResourcePreview[]][] {
|
||||
return this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)
|
||||
.map(s => ([s.resource, s.conflicts.map(toStrictResourcePreview)]));
|
||||
|
||||
@@ -14,13 +14,14 @@ import { IProductService, ConfigurationSyncStore } from 'vs/platform/product/com
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createCancelablePromise, timeout, CancelablePromise } from 'vs/base/common/async';
|
||||
import { isString, isObject, isArray } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
|
||||
const SYNC_SERVICE_URL_TYPE = 'sync.store.url.type';
|
||||
const SYNC_PREVIOUS_STORE = 'sync.previous.store';
|
||||
@@ -67,10 +68,10 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa
|
||||
const syncStore = value as ConfigurationSyncStore;
|
||||
const canSwitch = !!syncStore.canSwitch && !configuredStore?.url;
|
||||
const type: UserDataSyncStoreType | undefined = canSwitch ? this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL) as UserDataSyncStoreType : undefined;
|
||||
const url = configuredStore?.url
|
||||
|| type === 'insiders' ? syncStore.insidersUrl
|
||||
: type === 'stable' ? syncStore.stableUrl
|
||||
: syncStore.url;
|
||||
const url = configuredStore?.url ||
|
||||
(type === 'insiders' ? syncStore.insidersUrl
|
||||
: type === 'stable' ? syncStore.stableUrl
|
||||
: syncStore.url);
|
||||
return {
|
||||
url: URI.parse(url),
|
||||
type,
|
||||
@@ -111,7 +112,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
|
||||
|
||||
const syncStore = this.productService[CONFIGURATION_SYNC_STORE_KEY];
|
||||
if (syncStore) {
|
||||
this.storageService.store(SYNC_PREVIOUS_STORE, JSON.stringify(syncStore), StorageScope.GLOBAL);
|
||||
this.storageService.store(SYNC_PREVIOUS_STORE, JSON.stringify(syncStore), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.storageService.remove(SYNC_PREVIOUS_STORE, StorageScope.GLOBAL);
|
||||
}
|
||||
@@ -122,7 +123,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
|
||||
if (type === this.userDataSyncStore.defaultType) {
|
||||
this.storageService.remove(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL);
|
||||
} else {
|
||||
this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL);
|
||||
this.storageService.store(SYNC_SERVICE_URL_TYPE, type, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
this.updateUserDataSyncStore();
|
||||
}
|
||||
@@ -207,7 +208,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
}
|
||||
|
||||
if (this._donotMakeRequestsUntil) {
|
||||
this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL);
|
||||
this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
this.resetDonotMakeRequestsUntilPromise = createCancelablePromise(token => timeout(this._donotMakeRequestsUntil!.getTime() - Date.now(), token).then(() => this.setDonotMakeRequestsUntil(undefined)));
|
||||
} else {
|
||||
this.storageService.remove(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
|
||||
@@ -225,7 +226,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
const uri = joinPath(this.userDataSyncStoreUrl, 'resource', resource);
|
||||
const headers: IHeaders = {};
|
||||
|
||||
const context = await this.request({ type: 'GET', url: uri.toString(), headers }, [], CancellationToken.None);
|
||||
const context = await this.request(uri.toString(), { type: 'GET', headers }, [], CancellationToken.None);
|
||||
|
||||
const result = await asJson<{ url: string, created: number }[]>(context) || [];
|
||||
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
|
||||
@@ -240,7 +241,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
const headers: IHeaders = {};
|
||||
headers['Cache-Control'] = 'no-cache';
|
||||
|
||||
const context = await this.request({ type: 'GET', url, headers }, [], CancellationToken.None);
|
||||
const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None);
|
||||
const content = await asText(context);
|
||||
return content;
|
||||
}
|
||||
@@ -253,7 +254,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource).toString();
|
||||
const headers: IHeaders = {};
|
||||
|
||||
await this.request({ type: 'DELETE', url, headers }, [], CancellationToken.None);
|
||||
await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
|
||||
}
|
||||
|
||||
async read(resource: ServerResource, oldValue: IUserData | null, headers: IHeaders = {}): Promise<IUserData> {
|
||||
@@ -269,7 +270,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
headers['If-None-Match'] = oldValue.ref;
|
||||
}
|
||||
|
||||
const context = await this.request({ type: 'GET', url, headers }, [304], CancellationToken.None);
|
||||
const context = await this.request(url, { type: 'GET', headers }, [304], CancellationToken.None);
|
||||
|
||||
if (context.res.statusCode === 304) {
|
||||
// There is no new value. Hence return the old value.
|
||||
@@ -278,7 +279,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
|
||||
const ref = context.res.headers['etag'];
|
||||
if (!ref) {
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]);
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]);
|
||||
}
|
||||
const content = await asText(context);
|
||||
return { ref, content };
|
||||
@@ -296,11 +297,11 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
headers['If-Match'] = ref;
|
||||
}
|
||||
|
||||
const context = await this.request({ type: 'POST', url, data, headers }, [], CancellationToken.None);
|
||||
const context = await this.request(url, { type: 'POST', data, headers }, [], CancellationToken.None);
|
||||
|
||||
const newRef = context.res.headers['etag'];
|
||||
if (!newRef) {
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]);
|
||||
throw new UserDataSyncStoreError('Server did not return the ref', url, UserDataSyncErrorCode.NoRef, context.res.headers[HEADER_OPERATION_ID]);
|
||||
}
|
||||
return newRef;
|
||||
}
|
||||
@@ -314,7 +315,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
headers = { ...headers };
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
const context = await this.request({ type: 'GET', url, headers }, [], CancellationToken.None);
|
||||
const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None);
|
||||
|
||||
const manifest = await asJson<IUserDataManifest>(context);
|
||||
const currentSessionId = this.storageService.get(USER_SESSION_ID_KEY, StorageScope.GLOBAL);
|
||||
@@ -331,7 +332,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
|
||||
if (manifest) {
|
||||
// update session
|
||||
this.storageService.store(USER_SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
|
||||
this.storageService.store(USER_SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
return manifest;
|
||||
@@ -345,7 +346,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString();
|
||||
const headers: IHeaders = { 'Content-Type': 'text/plain' };
|
||||
|
||||
await this.request({ type: 'DELETE', url, headers }, [], CancellationToken.None);
|
||||
await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
|
||||
|
||||
// clear cached session.
|
||||
this.clearSession();
|
||||
@@ -356,13 +357,13 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private async request(options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise<IRequestContext> {
|
||||
private async request(url: string, options: IRequestOptions, successCodes: number[], token: CancellationToken): Promise<IRequestContext> {
|
||||
if (!this.authToken) {
|
||||
throw new UserDataSyncStoreError('No Auth Token Available', UserDataSyncErrorCode.Unauthorized, undefined);
|
||||
throw new UserDataSyncStoreError('No Auth Token Available', url, UserDataSyncErrorCode.Unauthorized, undefined);
|
||||
}
|
||||
|
||||
if (this._donotMakeRequestsUntil && Date.now() < this._donotMakeRequestsUntil.getTime()) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, undefined);
|
||||
}
|
||||
this.setDonotMakeRequestsUntil(undefined);
|
||||
|
||||
@@ -377,21 +378,23 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
// Add session headers
|
||||
this.addSessionHeaders(options.headers);
|
||||
|
||||
this.logService.trace('Sending request to server', { url: options.url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } });
|
||||
this.logService.trace('Sending request to server', { url, type: options.type, headers: { ...options.headers, ...{ authorization: undefined } } });
|
||||
|
||||
let context;
|
||||
try {
|
||||
context = await this.session.request(options, token);
|
||||
context = await this.session.request(url, options, token);
|
||||
} catch (e) {
|
||||
if (!(e instanceof UserDataSyncStoreError)) {
|
||||
e = new UserDataSyncStoreError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, undefined);
|
||||
const code = isPromiseCanceledError(e) ? UserDataSyncErrorCode.RequestCanceled
|
||||
: getErrorMessage(e).startsWith('XHR timeout') ? UserDataSyncErrorCode.RequestTimeout : UserDataSyncErrorCode.RequestFailed;
|
||||
e = new UserDataSyncStoreError(`Connection refused for the request '${url}'.`, url, code, undefined);
|
||||
}
|
||||
this.logService.info('Request failed', options.url);
|
||||
this.logService.info('Request failed', url);
|
||||
throw e;
|
||||
}
|
||||
|
||||
const operationId = context.res.headers[HEADER_OPERATION_ID];
|
||||
const requestInfo = { url: options.url, status: context.res.statusCode, 'execution-id': options.headers[HEADER_EXECUTION_ID], 'operation-id': operationId };
|
||||
const requestInfo = { url, status: context.res.statusCode, 'execution-id': options.headers[HEADER_EXECUTION_ID], 'operation-id': operationId };
|
||||
const isSuccess = isSuccessContext(context) || (context.res.statusCode && successCodes.indexOf(context.res.statusCode) !== -1);
|
||||
if (isSuccess) {
|
||||
this.logService.trace('Request succeeded', requestInfo);
|
||||
@@ -402,43 +405,43 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
if (context.res.statusCode === 401) {
|
||||
this.authToken = undefined;
|
||||
this._onTokenFailed.fire();
|
||||
throw new UserDataSyncStoreError(`Request '${options.url?.toString()}' failed because of Unauthorized (401).`, UserDataSyncErrorCode.Unauthorized, operationId);
|
||||
throw new UserDataSyncStoreError(`Request '${url}' failed because of Unauthorized (401).`, url, UserDataSyncErrorCode.Unauthorized, operationId);
|
||||
}
|
||||
|
||||
this._onTokenSucceed.fire();
|
||||
|
||||
if (context.res.statusCode === 409) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Conflict (409). There is new data for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Conflict, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Conflict (409). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.Conflict, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 410) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because the requested resource is not longer available (410).`, UserDataSyncErrorCode.Gone, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because the requested resource is not longer available (410).`, url, UserDataSyncErrorCode.Gone, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 412) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of Precondition Failed (412). There is new data for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.PreconditionFailed, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of Precondition Failed (412). There is new data for this resource. Make the request again with latest data.`, url, UserDataSyncErrorCode.PreconditionFailed, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 413) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too large payload (413).`, UserDataSyncErrorCode.TooLarge, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too large payload (413).`, url, UserDataSyncErrorCode.TooLarge, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 426) {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed with status Upgrade Required (426). Please upgrade the client and try again.`, UserDataSyncErrorCode.UpgradeRequired, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed with status Upgrade Required (426). Please upgrade the client and try again.`, url, UserDataSyncErrorCode.UpgradeRequired, operationId);
|
||||
}
|
||||
|
||||
if (context.res.statusCode === 429) {
|
||||
const retryAfter = context.res.headers['retry-after'];
|
||||
if (retryAfter) {
|
||||
this.setDonotMakeRequestsUntil(new Date(Date.now() + (parseInt(retryAfter) * 1000)));
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter, operationId);
|
||||
} else {
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
|
||||
throw new UserDataSyncStoreError(`${options.type} request '${url}' failed because of too many requests (429).`, url, UserDataSyncErrorCode.TooManyRequests, operationId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, UserDataSyncErrorCode.Unknown, operationId);
|
||||
throw new UserDataSyncStoreError('Server returned ' + context.res.statusCode, url, UserDataSyncErrorCode.Unknown, operationId);
|
||||
}
|
||||
|
||||
return context;
|
||||
@@ -448,7 +451,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
|
||||
let machineSessionId = this.storageService.get(MACHINE_SESSION_ID_KEY, StorageScope.GLOBAL);
|
||||
if (machineSessionId === undefined) {
|
||||
machineSessionId = generateUuid();
|
||||
this.storageService.store(MACHINE_SESSION_ID_KEY, machineSessionId, StorageScope.GLOBAL);
|
||||
this.storageService.store(MACHINE_SESSION_ID_KEY, machineSessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
headers['X-Machine-Session-Id'] = machineSessionId;
|
||||
|
||||
@@ -490,18 +493,20 @@ export class RequestsSession {
|
||||
private readonly logService: IUserDataSyncLogService,
|
||||
) { }
|
||||
|
||||
request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
|
||||
request(url: string, options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
|
||||
if (this.isExpired()) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
options.url = url;
|
||||
|
||||
if (this.requests.length >= this.limit) {
|
||||
this.logService.info('Too many requests', ...this.requests);
|
||||
throw new UserDataSyncStoreError(`Too many requests. Only ${this.limit} requests allowed in ${this.interval / (1000 * 60)} minutes.`, UserDataSyncErrorCode.LocalTooManyRequests, undefined);
|
||||
throw new UserDataSyncStoreError(`Too many requests. Only ${this.limit} requests allowed in ${this.interval / (1000 * 60)} minutes.`, url, UserDataSyncErrorCode.LocalTooManyRequests, undefined);
|
||||
}
|
||||
|
||||
this.startTime = this.startTime || new Date();
|
||||
this.requests.push(options.url!);
|
||||
this.requests.push(url);
|
||||
|
||||
return this.requestService.request(options, token);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import { NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
suite('GlobalStateMerge', () => {
|
||||
|
||||
test('merge when local and remote are same with one value', async () => {
|
||||
test('merge when local and remote are same with one value and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -21,11 +21,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
test('merge when local and remote are same with multiple entries and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -33,11 +33,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
test('merge when local and remote are same with multiple entries in different order and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -50,7 +50,7 @@ suite('GlobalStateMerge', () => {
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const base = { 'b': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -58,11 +58,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
test('merge when a new entry is added to remote and local has not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -70,11 +70,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
test('merge when multiple new entries are added to remote and local is not synced yet', async () => {
|
||||
const local = {};
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -86,7 +86,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -98,7 +98,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -110,7 +110,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = {};
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -122,7 +122,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
@@ -134,7 +134,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'd' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } });
|
||||
@@ -142,11 +142,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when new entries are added to local', async () => {
|
||||
test('merge when new entries are added to local and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -158,7 +158,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -170,7 +170,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -182,7 +182,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -194,7 +194,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'd' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -202,11 +202,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
test('merge when local and remote with one entry but different value and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
@@ -219,12 +219,12 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, ['b']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' }, 'b': { version: 1, value: 'd' } });
|
||||
});
|
||||
|
||||
test('merge with single entry and local is empty', async () => {
|
||||
@@ -232,32 +232,32 @@ suite('GlobalStateMerge', () => {
|
||||
const local = {};
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local and remote has moved forwareded with conflicts', async () => {
|
||||
test('merge when local and remote has moved forward with conflicts', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' } };
|
||||
const local = { 'a': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but not a registered key', async () => {
|
||||
test('merge when a new entry is added to remote but scoped to machine locally and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -265,23 +265,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but different version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated to remote but not a registered key', async () => {
|
||||
test('merge when an entry is updated to remote but scoped to machine locally', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: ['a'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -289,92 +277,43 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is updated to remote but different version', async () => {
|
||||
test('merge when a local value is removed and scoped to machine locally', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local moved forwared by changing a key to machine scope', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge should not remove remote keys if not registered', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: ['c'] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with lower version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'c' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with higher version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 2, value: 'c' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed but not registered', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with lower version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with higher version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a local value is not yet registered', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,10 +10,9 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
|
||||
suite('GlobalStateSync', () => {
|
||||
@@ -28,17 +27,11 @@ suite('GlobalStateSync', () => {
|
||||
setup(async () => {
|
||||
testClient = disposableStore.add(new UserDataSyncClient(server));
|
||||
await testClient.setUp(true);
|
||||
let storageKeysSyncRegistryService = testClient.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser;
|
||||
disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear()));
|
||||
|
||||
client2 = disposableStore.add(new UserDataSyncClient(server));
|
||||
await client2.setUp(true);
|
||||
storageKeysSyncRegistryService = client2.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
});
|
||||
|
||||
teardown(() => disposableStore.clear());
|
||||
@@ -72,7 +65,7 @@ suite('GlobalStateSync', () => {
|
||||
|
||||
test('when global state is created after first sync', async () => {
|
||||
await testObject.sync(await testClient.manifest());
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
|
||||
let lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const manifest = await testClient.manifest();
|
||||
@@ -91,7 +84,8 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync - outgoing to server (no state)', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
updateMachineStorage('b', 'value1', testClient);
|
||||
await updateLocale(testClient);
|
||||
|
||||
await testObject.sync(await testClient.manifest());
|
||||
@@ -105,7 +99,7 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync - incoming from server (no state)', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await updateLocale(client2);
|
||||
await client2.sync();
|
||||
|
||||
@@ -118,10 +112,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync when storage exists', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -136,10 +130,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync when storage exists - has conflicts', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('a', 'value2', client2);
|
||||
updateUserStorage('a', 'value2', client2);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
@@ -154,10 +148,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync adding a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -172,10 +166,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync updating a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
updateStorage('a', 'value2', testClient);
|
||||
updateUserStorage('a', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -189,8 +183,8 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync removing a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
removeStorage('b', testClient);
|
||||
@@ -218,9 +212,14 @@ suite('GlobalStateSync', () => {
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' })));
|
||||
}
|
||||
|
||||
function updateStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
function updateUserStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.store(key, value, StorageScope.GLOBAL);
|
||||
storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
function updateMachineStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
function removeStorage(key: string, client: UserDataSyncClient): void {
|
||||
|
||||
@@ -36,10 +36,10 @@ import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/plat
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
|
||||
import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions';
|
||||
import { ExtensionsStorageSyncService, IExtensionsStorageSyncService } from 'vs/platform/userDataSync/common/extensionsStorageSync';
|
||||
|
||||
export class UserDataSyncClient extends Disposable {
|
||||
|
||||
@@ -104,9 +104,9 @@ export class UserDataSyncClient extends Disposable {
|
||||
this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService));
|
||||
this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService());
|
||||
this.instantiationService.stub(IUserDataSyncResourceEnablementService, this.instantiationService.createInstance(UserDataSyncResourceEnablementService));
|
||||
this.instantiationService.stub(IStorageKeysSyncRegistryService, this.instantiationService.createInstance(StorageKeysSyncRegistryService));
|
||||
|
||||
this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService));
|
||||
this.instantiationService.stub(IExtensionsStorageSyncService, this.instantiationService.createInstance(ExtensionsStorageSyncService));
|
||||
this.instantiationService.stub(IIgnoredExtensionsManagementService, this.instantiationService.createInstance(IgnoredExtensionsManagementService));
|
||||
this.instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||
async getInstalled() { return []; },
|
||||
|
||||
@@ -464,10 +464,10 @@ suite('UserDataSyncRequestsSession', () => {
|
||||
|
||||
test('too many requests are thrown when limit exceeded', async () => {
|
||||
const testObject = new RequestsSession(1, 500, requestService, new NullLogService());
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
|
||||
try {
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
} catch (error) {
|
||||
assert.ok(error instanceof UserDataSyncStoreError);
|
||||
assert.equal((<UserDataSyncStoreError>error).code, UserDataSyncErrorCode.LocalTooManyRequests);
|
||||
@@ -478,19 +478,19 @@ suite('UserDataSyncRequestsSession', () => {
|
||||
|
||||
test('requests are handled after session is expired', async () => {
|
||||
const testObject = new RequestsSession(1, 500, requestService, new NullLogService());
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
await timeout(600);
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
});
|
||||
|
||||
test('too many requests are thrown after session is expired', async () => {
|
||||
const testObject = new RequestsSession(1, 500, requestService, new NullLogService());
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
await timeout(600);
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
|
||||
try {
|
||||
await testObject.request({}, CancellationToken.None);
|
||||
await testObject.request('url', {}, CancellationToken.None);
|
||||
} catch (error) {
|
||||
assert.ok(error instanceof UserDataSyncStoreError);
|
||||
assert.equal((<UserDataSyncStoreError>error).code, UserDataSyncErrorCode.LocalTooManyRequests);
|
||||
|
||||
Reference in New Issue
Block a user