Update to VS Code 1.52.1

This commit is contained in:
Asher
2021-02-09 16:08:37 +00:00
1351 changed files with 56560 additions and 38990 deletions

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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> } {

View File

@@ -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);
}
}
}

View File

@@ -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 || '{}');

View File

@@ -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`);
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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) { }

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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)]));

View File

@@ -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);
}

View File

@@ -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' } });
});
});

View File

@@ -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 {

View File

@@ -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 []; },

View File

@@ -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);