Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)

* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79

* Fix breaks

* Extension management fixes

* Fix breaks in windows bundling

* Fix/skip failing tests

* Update distro

* Add clear to nuget.config

* Add hygiene task

* Bump distro

* Fix hygiene issue

* Add build to hygiene exclusion

* Update distro

* Update hygiene

* Hygiene exclusions

* Update tsconfig

* Bump distro for server breaks

* Update build config

* Update darwin path

* Add done calls to notebook tests

* Skip failing tests

* Disable smoke tests
This commit is contained in:
Karl Burtram
2021-02-09 16:15:05 -08:00
committed by GitHub
parent 6f192f9af5
commit ce612a3d96
1929 changed files with 68012 additions and 34564 deletions

View File

@@ -4,16 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { IFileService, IFileContent, FileChangesEvent, FileOperationResult, FileOperationError, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import {
SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService,
IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, ISyncResourcePreview as IBaseSyncResourcePreview,
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState, IUserDataInitializer
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
import { IExtUri, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
import { CancelablePromise, RunOnceScheduler, createCancelablePromise } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -53,8 +53,8 @@ function isSyncData(thing: any): thing is ISyncData {
return false;
}
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI {
return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService, extUri: IExtUri): URI {
return extUri.joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
}
export interface IResourcePreview {
@@ -100,6 +100,7 @@ export abstract class AbstractSynchroniser extends Disposable {
protected readonly syncFolder: URI;
protected readonly syncPreviewFolder: URI;
protected readonly extUri: IExtUri;
private readonly currentMachineIdPromise: Promise<string>;
private _status: SyncStatus = SyncStatus.Idle;
@@ -135,9 +136,10 @@ export abstract class AbstractSynchroniser extends Disposable {
) {
super();
this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService);
this.extUri = this.fileService.hasCapability(environmentService.userDataSyncHome, FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase;
this.syncFolder = this.extUri.joinPath(environmentService.userDataSyncHome, resource);
this.syncPreviewFolder = this.extUri.joinPath(this.syncFolder, PREVIEW_DIR_NAME);
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService, this.extUri);
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
}
@@ -423,7 +425,7 @@ export abstract class AbstractSynchroniser extends Disposable {
let preview = await this.syncPreviewPromise;
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
this.extUri.isEqual(localResource, resource) || this.extUri.isEqual(remoteResource, resource) || this.extUri.isEqual(previewResource, resource));
if (index === -1) {
return;
}
@@ -483,7 +485,7 @@ export abstract class AbstractSynchroniser extends Disposable {
private updateConflicts(resourcePreviews: IEditableResourcePreview[]): void {
const conflicts = resourcePreviews.filter(({ mergeState }) => mergeState === MergeState.Conflict);
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
if (!equals(this._conflicts, conflicts, (a, b) => this.extUri.isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
}
@@ -513,8 +515,8 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async getMachineId({ uri }: ISyncResourceHandle): Promise<string | undefined> {
const ref = basename(uri);
if (isEqual(uri, this.toRemoteBackupResource(ref))) {
const ref = this.extUri.basename(uri);
if (this.extUri.isEqual(uri, this.toRemoteBackupResource(ref))) {
const { content } = await this.getUserData(ref);
if (content) {
const syncData = this.parseSyncData(content);
@@ -525,12 +527,12 @@ export abstract class AbstractSynchroniser extends Disposable {
}
async resolveContent(uri: URI): Promise<string | null> {
const ref = basename(uri);
if (isEqual(uri, this.toRemoteBackupResource(ref))) {
const ref = this.extUri.basename(uri);
if (this.extUri.isEqual(uri, this.toRemoteBackupResource(ref))) {
const { content } = await this.getUserData(ref);
return content;
}
if (isEqual(uri, this.toLocalBackupResource(ref))) {
if (this.extUri.isEqual(uri, this.toLocalBackupResource(ref))) {
return this.userDataSyncBackupStoreService.resolveContent(this.resource, ref);
}
return null;
@@ -540,13 +542,13 @@ export abstract class AbstractSynchroniser extends Disposable {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (isEqual(resourcePreview.acceptedResource, uri)) {
if (this.extUri.isEqual(resourcePreview.acceptedResource, uri)) {
return resourcePreview.acceptResult ? resourcePreview.acceptResult.content : null;
}
if (isEqual(resourcePreview.remoteResource, uri)) {
if (this.extUri.isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent;
}
if (isEqual(resourcePreview.localResource, uri)) {
if (this.extUri.isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent;
}
}
@@ -727,7 +729,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
@IConfigurationService configurationService: IConfigurationService,
) {
super(resource, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
this._register(this.fileService.watch(dirname(file)));
this._register(this.fileService.watch(this.extUri.dirname(file)));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
@@ -802,8 +804,9 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
}
export abstract class AbstractInitializer {
export abstract class AbstractInitializer implements IUserDataInitializer {
protected readonly extUri: IExtUri;
private readonly lastSyncResource: URI;
constructor(
@@ -812,7 +815,8 @@ export abstract class AbstractInitializer {
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
@IFileService protected readonly fileService: IFileService,
) {
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService);
this.extUri = this.fileService.hasCapability(environmentService.userDataSyncHome, FileSystemProviderCapabilities.PathCaseSensitive) ? extUri : extUriIgnorePathCase;
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService, extUri);
}
async initialize({ ref, content }: IUserData): Promise<void> {

View File

@@ -3,24 +3,23 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { ISyncExtension, ISyncExtensionWithVersion } from 'vs/platform/userDataSync/common/userDataSync';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { deepClone } from 'vs/base/common/objects';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { distinct } from 'vs/base/common/arrays';
import { deepClone, equals } from 'vs/base/common/objects';
import { IStringDictionary } from 'vs/base/common/collections';
import * as semver from 'vs/base/common/semver/semver';
export interface IMergeResult {
added: ISyncExtension[];
removed: IExtensionIdentifier[];
updated: ISyncExtension[];
updated: ISyncExtensionWithVersion[];
remote: ISyncExtension[] | null;
}
export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[]): IMergeResult {
export function merge(localExtensions: ISyncExtensionWithVersion[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[]): IMergeResult {
const added: ISyncExtension[] = [];
const removed: IExtensionIdentifier[] = [];
const updated: ISyncExtension[] = [];
const updated: ISyncExtensionWithVersion[] = [];
if (!remoteExtensions) {
const remote = localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase()));
@@ -48,17 +47,23 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
return uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
};
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const addExtensionToMap = <T extends ISyncExtension>(map: Map<string, T>, extension: T) => {
map.set(getKey(extension), extension);
return map;
};
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const localExtensionsMap: Map<string, ISyncExtensionWithVersion> = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtensionWithVersion>());
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce((map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const key = getKey(extension);
extension = deepClone(extension);
if (localExtensionsMap.get(key)?.installed) {
extension.installed = true;
const localExtension = localExtensionsMap.get(key);
if (localExtension) {
if (localExtension.installed) {
extension.installed = true;
}
if (!extension.version) {
extension.version = localExtension.version;
}
}
return addExtensionToMap(map, extension);
}, new Map<string, ISyncExtension>());
@@ -75,6 +80,20 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
const merge = (key: string, updatedInRemote: boolean): ISyncExtensionWithVersion | undefined => {
const localExtension = localExtensionsMap.get(key);
if (localExtension) {
const remoteExtension = remoteExtensionsMap.get(key)!;
return {
...(updatedInRemote ? remoteExtension : localExtension),
version: remoteExtension.version && semver.gt(remoteExtension.version, localExtension.version) ? localExtension.version : localExtension.version,
state: mergeExtensionState(localExtension, remoteExtension, lastSyncExtensionsMap?.get(key))
};
}
return undefined;
};
// Remotely removed extension.
for (const key of baseToRemote.removed.values()) {
const e = localExtensionsMap.get(key);
@@ -89,7 +108,11 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
const mergedExtension = merge(key, true);
if (mergedExtension) {
updated.push(massageOutgoingExtension(mergedExtension, key));
newRemoteExtensionsMap.set(key, mergedExtension);
}
}
} else {
// Add only installed extension to local
@@ -103,7 +126,11 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
// Remotely updated extensions
for (const key of baseToRemote.updated.values()) {
// Update in local always
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
const mergedExtension = merge(key, true);
if (mergedExtension) {
updated.push(massageOutgoingExtension(mergedExtension, key));
newRemoteExtensionsMap.set(key, mergedExtension);
}
}
// Locally added extensions
@@ -123,12 +150,14 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
const extension = deepClone(localExtensionsMap.get(key)!);
// Retain installed property
if (newRemoteExtensionsMap.get(key)?.installed) {
extension.installed = true;
const mergedExtension = merge(key, false);
if (mergedExtension) {
// Retain installed property
if (newRemoteExtensionsMap.get(key)?.installed) {
mergedExtension.installed = true;
}
newRemoteExtensionsMap.set(key, mergedExtension);
}
newRemoteExtensionsMap.set(key, extension);
}
}
@@ -145,7 +174,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
}
const remote: ISyncExtension[] = [];
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>(), { checkInstalledProperty: true });
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>(), { checkInstalledProperty: true, checkVersionProperty: true });
if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) {
newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key)));
}
@@ -153,7 +182,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
return { added, removed, updated, remote: remote.length ? remote : null };
}
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>, { checkInstalledProperty }: { checkInstalledProperty: boolean } = { checkInstalledProperty: false }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>, { checkInstalledProperty, checkVersionProperty }: { checkInstalledProperty: boolean, checkVersionProperty: boolean } = { checkInstalledProperty: false, checkVersionProperty: false }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = from ? [...from.keys()].filter(key => !ignoredExtensions.has(key)) : [];
const toKeys = [...to.keys()].filter(key => !ignoredExtensions.has(key));
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
@@ -168,7 +197,8 @@ function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISync
const toExtension = to.get(key);
if (!toExtension
|| fromExtension.disabled !== toExtension.disabled
|| fromExtension.version !== toExtension.version
|| !isSameExtensionState(fromExtension.state, toExtension.state)
|| (checkVersionProperty && fromExtension.version !== toExtension.version)
|| (checkInstalledProperty && fromExtension.installed !== toExtension.installed)
) {
updated.add(key);
@@ -178,55 +208,104 @@ function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISync
return { added, removed, updated };
}
function mergeExtensionState(localExtension: ISyncExtensionWithVersion, remoteExtension: ISyncExtension, lastSyncExtension: ISyncExtension | undefined): IStringDictionary<any> | undefined {
const localState = localExtension.state;
const remoteState = remoteExtension.state;
const baseState = lastSyncExtension?.state;
// If remote extension has no version, use local state
if (!remoteExtension.version) {
return localState;
}
// If local state exists and local extension is latest then use local state
if (localState && semver.gt(localExtension.version, remoteExtension.version)) {
return localState;
}
// If remote state exists and remote extension is latest, use remote state
if (remoteState && semver.gt(remoteExtension.version, localExtension.version)) {
return remoteState;
}
/* Remote and local are on same version */
// If local state is not yet set, use remote state
if (!localState) {
return remoteState;
}
// If remote state is not yet set, use local state
if (!remoteState) {
return localState;
}
const mergedState: IStringDictionary<any> = deepClone(localState);
const baseToRemote = baseState ? compareExtensionState(baseState, remoteState) : { added: Object.keys(remoteState).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToLocal = baseState ? compareExtensionState(baseState, localState) : { added: Object.keys(localState).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
// Added/Updated in remote
for (const key of [...baseToRemote.added.values(), ...baseToRemote.updated.values()]) {
mergedState[key] = remoteState[key];
}
// Removed in remote
for (const key of baseToRemote.removed.values()) {
// Not updated in local
if (!baseToLocal.updated.has(key)) {
delete mergedState[key];
}
}
return mergedState;
}
function compareExtensionState(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = Object.keys(from);
const toKeys = Object.keys(to);
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const value1 = from[key];
const value2 = to[key];
if (!equals(value1, value2)) {
updated.add(key);
}
}
return { added, removed, updated };
}
function isSameExtensionState(a: IStringDictionary<any> = {}, b: IStringDictionary<any> = {}): boolean {
const { added, removed, updated } = compareExtensionState(a, b);
return added.size === 0 && removed.size === 0 && updated.size === 0;
}
// massage incoming extension - add optional properties
function massageIncomingExtension(extension: ISyncExtension): ISyncExtension {
function massageIncomingExtension<T extends ISyncExtension>(extension: T): T {
return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed } };
}
// massage outgoing extension - remove optional properties
function massageOutgoingExtension(extension: ISyncExtension, key: string): ISyncExtension {
function massageOutgoingExtension<T extends ISyncExtension>(extension: T, key: string): T {
const massagedExtension: ISyncExtension = {
identifier: {
id: extension.identifier.id,
uuid: key.startsWith('uuid:') ? key.substring('uuid:'.length) : undefined
},
};
if (extension.version) {
massagedExtension.version = extension.version;
}
if (extension.disabled) {
massagedExtension.disabled = true;
}
if (extension.installed) {
massagedExtension.installed = true;
}
if (extension.version) {
massagedExtension.version = extension.version;
if (extension.state) {
massagedExtension.state = extension.state;
}
return massagedExtension;
}
export function getIgnoredExtensions(installed: ILocalExtension[], configurationService: IConfigurationService): string[] {
const defaultIgnoredExtensions = installed.filter(i => i.isMachineScoped).map(i => i.identifier.id.toLowerCase());
const value = getConfiguredIgnoredExtensions(configurationService).map(id => id.toLowerCase());
const added: string[] = [], removed: string[] = [];
if (Array.isArray(value)) {
for (const key of value) {
if (key.startsWith('-')) {
removed.push(key.substring(1));
} else {
added.push(key);
}
}
}
return distinct([...defaultIgnoredExtensions, ...added,].filter(setting => removed.indexOf(setting) === -1));
}
function getConfiguredIgnoredExtensions(configurationService: IConfigurationService): string[] {
let userValue = configurationService.inspect<string[]>('settingsSync.ignoredExtensions').userValue;
if (userValue !== undefined) {
return userValue;
}
userValue = configurationService.inspect<string[]>('sync.ignoredExtensions').userValue;
if (userValue !== undefined) {
return userValue;
}
return configurationService.getValue<string[]>('settingsSync.ignoredExtensions') || [];
return massagedExtension as T;
}

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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

@@ -5,7 +5,7 @@
import {
IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService,
IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, IRemoteUserData, ISyncData, Change
IUserDataSyncBackupStoreService, ISyncResourceHandle, USER_DATA_SYNC_SCHEME, IRemoteUserData, ISyncData, Change, ISyncExtensionWithVersion
} from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -14,16 +14,19 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources';
import { format } from 'vs/base/common/jsonFormatter';
import { applyEdits } from 'vs/base/common/jsonEdit';
import { compare } from 'vs/base/common/strings';
import { IStorageService } 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 { 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[];
@@ -33,7 +36,7 @@ interface IExtensionResourceMergeResult extends IAcceptResult {
}
interface IExtensionResourcePreview extends IResourcePreview {
readonly localExtensions: ISyncExtension[];
readonly localExtensions: ISyncExtensionWithVersion[];
readonly skippedExtensions: ISyncExtension[];
readonly previewResult: IExtensionResourceMergeResult;
}
@@ -47,7 +50,7 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen
if (syncData.version === 1
|| syncData.version === 2
) {
const systemExtensions = await extensionManagementService.getInstalled(ExtensionType.System);
const builtinExtensions = (await extensionManagementService.getInstalled(ExtensionType.System)).filter(e => e.isBuiltin);
for (const extension of extensions) {
// #region Migration from v1 (enabled -> disabled)
if (syncData.version === 1) {
@@ -60,7 +63,7 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen
// #region Migration from v2 (set installed property on extension)
if (syncData.version === 2) {
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
if (builtinExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
extension.installed = true;
}
}
@@ -79,10 +82,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected readonly version: number = 3;
*/
/* Version 4: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
protected readonly version: number = 4;
/* Version 5: Introduce extension state */
protected readonly version: number = 5;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@@ -90,16 +94,18 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IFileService fileService: IFileService,
@IStorageService storageService: IStorageService,
@IStorageService private readonly storageService: IStorageService,
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IIgnoredExtensionsManagementService private readonly extensionSyncManagementService: IIgnoredExtensionsManagementService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionsStorageSyncService private readonly extensionsStorageSyncService: IExtensionsStorageSyncService,
) {
super(SyncResource.Extensions, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, configurationService);
this._register(
@@ -107,18 +113,19 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
Event.any<any>(
Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)),
Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)),
this.extensionEnablementService.onDidChangeEnablement),
this.extensionEnablementService.onDidChangeEnablement,
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);
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
if (remoteExtensions) {
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`);
@@ -183,17 +190,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected async getAcceptResult(resourcePreview: IExtensionResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IExtensionResourceMergeResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return this.acceptLocal(resourcePreview);
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return this.acceptRemote(resourcePreview);
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
return resourcePreview.previewResult;
}
@@ -202,7 +209,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions);
const { added, removed, updated, remote } = mergeResult;
return {
@@ -218,7 +225,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
if (remoteExtensions !== null) {
const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions);
@@ -272,18 +279,18 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
return [{ resource: joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }];
return [{ resource: this.extUri.joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
return this.format(localExtensions);
}
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
@@ -292,11 +299,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'extensions.json':
return this.format(this.parseExtensions(syncData));
}
@@ -337,13 +344,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
const removeFromSkipped: IExtensionIdentifier[] = [];
const addToSkipped: ISyncExtension[] = [];
const installedExtensions = await this.extensionManagementService.getInstalled();
if (removed.length) {
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
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);
}));
@@ -351,11 +358,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (added.length || updated.length) {
await Promise.all([...added, ...updated].map(async e => {
const installedExtensions = await this.extensionManagementService.getInstalled();
const installedExtension = installedExtensions.filter(installed => areSameExtensions(installed.identifier, e.identifier))[0];
const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier));
// Builtin Extension: Sync only enablement state
if (installedExtension && installedExtension.type === ExtensionType.System) {
// Builtin Extension Sync: Enablement & State
if (installedExtension && installedExtension.isBuiltin) {
if (e.state && installedExtension.manifest.version === e.version) {
this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version);
}
if (e.disabled) {
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
await this.extensionEnablementService.disableExtension(e.identifier);
@@ -369,7 +378,20 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return;
}
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version);
// User Extension Sync: Install/Update, Enablement & State
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier);
/* Update extension state only if
* extension is installed and version is same as synced version or
* extension is not installed and installable
*/
if (e.state &&
(installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */
: !!extension /* Installable */)
) {
this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version);
}
if (extension) {
try {
if (e.disabled) {
@@ -381,10 +403,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
await this.extensionEnablementService.enableExtension(extension.identifier);
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
}
// Install only if the extension does not exist
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
if (!installedExtension) {
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension);
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);
}
@@ -413,21 +436,47 @@ 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);
}
private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtension[] {
private getLocalExtensions(installedExtensions: ILocalExtension[]): ISyncExtensionWithVersion[] {
const disabledExtensions = this.extensionEnablementService.getDisabledExtensions();
return installedExtensions
.map(({ identifier, type }) => {
const syncExntesion: ISyncExtension = { identifier };
.map(({ identifier, isBuiltin, manifest }) => {
const syncExntesion: ISyncExtensionWithVersion = { identifier, version: manifest.version };
if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) {
syncExntesion.disabled = true;
}
if (type === ExtensionType.User) {
if (!isBuiltin) {
syncExntesion.installed = true;
}
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)) {
state[key] = extensionStorageState[key];
}
return state;
}, {});
}
} catch (error) {
this.logService.info(`${this.syncResourceLogLabel}: Error while parsing extension state`, getErrorMessage(error));
}
return syncExntesion;
});
}
@@ -440,6 +489,7 @@ export class ExtensionsInitializer extends AbstractInitializer {
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IStorageService private readonly storageService: IStorageService,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@@ -454,16 +504,25 @@ 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[] = [];
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
const toDisable: IExtensionIdentifier[] = [];
for (const extension of remoteExtensions) {
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
installedExtensionsToSync.push(extension);
if (extension.disabled) {
toDisable.push(extension.identifier);
}
} else {
if (extension.installed) {
newExtensionsToSync.set(extension.identifier.id.toLowerCase(), extension);
if (extension.identifier.uuid) {
toInstall.uuids.push(extension.identifier.uuid);
} else {
@@ -477,8 +536,15 @@ export class ExtensionsInitializer extends AbstractInitializer {
const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage;
for (const galleryExtension of galleryExtensions) {
try {
const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!;
if (extensionToSync.state) {
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);
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);
@@ -493,8 +559,16 @@ export class ExtensionsInitializer extends AbstractInitializer {
this.logService.info(`Enabled extension`, identifier.id);
}
}
for (const extensionToSync of installedExtensionsToSync) {
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, 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

@@ -10,7 +10,6 @@ import {
import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { dirname, joinPath, basename, isEqual } from 'vs/base/common/resources';
import { IFileService } from 'vs/platform/files/common/files';
import { IStringDictionary } from 'vs/base/common/collections';
import { edit } from 'vs/platform/userDataSync/common/content';
@@ -22,34 +21,39 @@ 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` });
protected readonly version: number = 1;
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@@ -64,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(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;
@@ -92,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,
@@ -102,7 +106,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
};
return [{
skippedStorageKeys: skipped,
localResource: this.localResource,
localContent: this.format(localGloablState),
localUserData: localGloablState,
@@ -113,6 +116,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: this.acceptedResource,
storageKeys
}];
}
@@ -123,17 +127,17 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
protected async getAcceptResult(resourcePreview: IGlobalStateResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IGlobalStateResourceMergeResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return this.acceptLocal(resourcePreview);
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return this.acceptRemote(resourcePreview);
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
return resourcePreview.previewResult;
}
@@ -153,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,
@@ -172,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) {
@@ -196,25 +200,25 @@ 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`);
}
}
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
return [{ resource: joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }];
return [{ resource: this.extUri.joinPath(uri, 'globalState.json'), comparableResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
if (this.extUri.isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
const localGlobalState = await this.getLocalGlobalState();
return this.format(localGlobalState);
}
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
@@ -223,11 +227,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'globalState.json':
return this.format(JSON.parse(syncData.content));
}
@@ -238,7 +242,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
private format(globalState: IGlobalState): string {
const storageKeys = Object.keys(globalState.storage).sort();
const storageKeys = globalState.storage ? Object.keys(globalState.storage).sort() : [];
const storage: IStringDictionary<IStorageValue> = {};
storageKeys.forEach(key => storage[key] = globalState.storage[key]);
globalState.storage = storage;
@@ -268,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 };
@@ -318,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));
}
@@ -337,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 };
}
}
@@ -386,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

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { distinct } from 'vs/base/common/arrays';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IIgnoredExtensionsManagementService = createDecorator<IIgnoredExtensionsManagementService>('IIgnoredExtensionsManagementService');
export interface IIgnoredExtensionsManagementService {
readonly _serviceBrand: any;
getIgnoredExtensions(installed: ILocalExtension[]): string[];
hasToNeverSyncExtension(extensionId: string): boolean;
hasToAlwaysSyncExtension(extensionId: string): boolean;
updateIgnoredExtensions(ignoredExtensionId: string, ignore: boolean): Promise<void>;
updateSynchronizedExtensions(ignoredExtensionId: string, sync: boolean): Promise<void>;
}
export class IgnoredExtensionsManagementService implements IIgnoredExtensionsManagementService {
declare readonly _serviceBrand: undefined;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
) {
}
hasToNeverSyncExtension(extensionId: string): boolean {
const configuredIgnoredExtensions = this.getConfiguredIgnoredExtensions();
return configuredIgnoredExtensions.includes(extensionId.toLowerCase());
}
hasToAlwaysSyncExtension(extensionId: string): boolean {
const configuredIgnoredExtensions = this.getConfiguredIgnoredExtensions();
return configuredIgnoredExtensions.includes(`-${extensionId.toLowerCase()}`);
}
updateIgnoredExtensions(ignoredExtensionId: string, ignore: boolean): Promise<void> {
// first remove the extension completely from ignored extensions
let currentValue = [...this.configurationService.getValue<string[]>('settingsSync.ignoredExtensions')].map(id => id.toLowerCase());
currentValue = currentValue.filter(v => v !== ignoredExtensionId && v !== `-${ignoredExtensionId}`);
// Add only if ignored
if (ignore) {
currentValue.push(ignoredExtensionId.toLowerCase());
}
return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
}
updateSynchronizedExtensions(extensionId: string, sync: boolean): Promise<void> {
// first remove the extension completely from ignored extensions
let currentValue = [...this.configurationService.getValue<string[]>('settingsSync.ignoredExtensions')].map(id => id.toLowerCase());
currentValue = currentValue.filter(v => v !== extensionId && v !== `-${extensionId}`);
// Add only if synced
if (sync) {
currentValue.push(`-${extensionId.toLowerCase()}`);
}
return this.configurationService.updateValue('settingsSync.ignoredExtensions', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
}
getIgnoredExtensions(installed: ILocalExtension[]): string[] {
const defaultIgnoredExtensions = installed.filter(i => i.isMachineScoped).map(i => i.identifier.id.toLowerCase());
const value = this.getConfiguredIgnoredExtensions().map(id => id.toLowerCase());
const added: string[] = [], removed: string[] = [];
if (Array.isArray(value)) {
for (const key of value) {
if (key.startsWith('-')) {
removed.push(key.substring(1));
} else {
added.push(key);
}
}
}
return distinct([...defaultIgnoredExtensions, ...added,].filter(setting => removed.indexOf(setting) === -1));
}
private getConfiguredIgnoredExtensions(): string[] {
let userValue = this.configurationService.inspect<string[]>('settingsSync.ignoredExtensions').userValue;
if (userValue !== undefined) {
return userValue;
}
userValue = this.configurationService.inspect<string[]>('sync.ignoredExtensions').userValue;
if (userValue !== undefined) {
return userValue;
}
return (this.configurationService.getValue<string[]>('settingsSync.ignoredExtensions') || []).map(id => id.toLowerCase());
}
}

View File

@@ -21,9 +21,9 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
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;
@@ -36,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) {
@@ -55,7 +59,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
protected readonly version: number = 2;
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'keybindings.json');
private readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
private readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
private readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@@ -73,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();
@@ -149,7 +154,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
protected async getAcceptResult(resourcePreview: IKeybindingsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return {
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
localChange: Change.None,
@@ -158,7 +163,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return {
content: resourcePreview.remoteContent,
localChange: Change.Modified,
@@ -167,7 +172,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@@ -205,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`);
@@ -213,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`);
}
@@ -225,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`);
}
@@ -258,22 +255,22 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
const comparableResource = (await this.fileService.exists(this.file)) ? this.file : this.localResource;
return [{ resource: joinPath(uri, 'keybindings.json'), comparableResource }];
return [{ resource: this.extUri.joinPath(uri, 'keybindings.json'), comparableResource }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
if (content) {
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'keybindings.json':
return this.getKeybindingsContentFromSyncContent(syncData.content);
}
@@ -282,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());
@@ -291,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';
@@ -21,7 +21,6 @@ import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFile
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Edit } from 'vs/base/common/jsonFormatter';
import { setProperty, applyEdits } from 'vs/base/common/jsonEdit';
@@ -49,7 +48,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
protected readonly version: number = 2;
readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'settings.json');
readonly localResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' });
readonly remoteResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' });
readonly acceptedResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' });
@@ -141,7 +140,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const ignoredSettings = await this.getIgnoredSettings();
/* Accept local resource */
if (isEqual(resource, this.localResource)) {
if (this.extUri.isEqual(resource, this.localResource)) {
return {
/* Remove ignored settings */
content: resourcePreview.fileContent ? updateIgnoredSettings(resourcePreview.fileContent.value.toString(), '{}', ignoredSettings, formattingOptions) : null,
@@ -151,7 +150,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
/* Accept remote resource */
if (isEqual(resource, this.remoteResource)) {
if (this.extUri.isEqual(resource, this.remoteResource)) {
return {
/* Update ignored settings from local file content */
content: resourcePreview.remoteContent !== null ? updateIgnoredSettings(resourcePreview.remoteContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formattingOptions) : null,
@@ -161,7 +160,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
/* Accept preview resource */
if (isEqual(resource, this.previewResource)) {
if (this.extUri.isEqual(resource, this.previewResource)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@@ -199,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`);
}
@@ -244,24 +244,24 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource: URI }[]> {
const comparableResource = (await this.fileService.exists(this.file)) ? this.file : this.localResource;
return [{ resource: joinPath(uri, 'settings.json'), comparableResource }];
return [{ resource: this.extUri.joinPath(uri, 'settings.json'), comparableResource }];
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
if (this.extUri.isEqual(this.remoteResource, uri) || this.extUri.isEqual(this.localResource, uri) || this.extUri.isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
if (content) {
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
const settingsSyncContent = this.parseSettingsSyncContent(syncData.content);
if (settingsSyncContent) {
switch (basename(uri)) {
switch (this.extUri.basename(uri)) {
case 'settings.json':
return settingsSyncContent.settings;
}

View File

@@ -8,18 +8,18 @@ import {
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, Change
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IFileService, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStringDictionary } from 'vs/base/common/collections';
import { URI } from 'vs/base/common/uri';
import { joinPath, extname, relativePath, isEqualOrParent, basename, dirname } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { merge, IMergeResult as ISnippetsMergeResult, areSame } from 'vs/platform/userDataSync/common/snippetsMerge';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { deepClone } from 'vs/base/common/objects';
import { Event } from 'vs/base/common/event';
interface ISnippetsResourcePreview extends IFileResourcePreview {
previewResult: IMergeResult;
@@ -49,14 +49,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
this.snippetsFolder = environmentService.snippetsHome;
this._register(this.fileService.watch(environmentService.userRoamingDataHome));
this._register(this.fileService.watch(this.snippetsFolder));
this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e)));
}
private onFileChanges(e: FileChangesEvent): void {
if (!e.changes.some(change => isEqualOrParent(change.resource, this.snippetsFolder))) {
return;
}
this.triggerLocalChange();
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.affects(this.snippetsFolder))(() => this.triggerLocalChange()));
}
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<ISnippetsResourcePreview[]> {
@@ -82,7 +75,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
protected async getAcceptResult(resourcePreview: ISnippetsResourcePreview, resource: URI, content: string | null | undefined, token: CancellationToken): Promise<IAcceptResult> {
/* Accept local resource */
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))) {
return {
content: resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : null,
localChange: Change.None,
@@ -93,7 +86,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
/* Accept remote resource */
if (isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))) {
return {
content: resourcePreview.remoteContent,
localChange: resourcePreview.remoteContent !== null
@@ -104,7 +97,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
/* Accept preview resource */
if (isEqualOrParent(resource, this.syncPreviewFolder)) {
if (this.extUri.isEqualOrParent(resource, this.syncPreviewFolder)) {
if (content === undefined) {
return {
content: resourcePreview.previewResult.content,
@@ -172,15 +165,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
};
resourcePreviews.set(key, {
fileContent: null,
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -193,16 +186,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -215,16 +208,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -237,16 +230,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Added,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -259,16 +252,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Modified,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key],
localContent: localFileContent[key].value.toString(),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -281,16 +274,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.Deleted,
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: null,
localContent: null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -303,16 +296,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
@@ -326,16 +319,16 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
remoteChange: Change.None
};
resourcePreviews.set(key, {
localResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
localResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }),
fileContent: localFileContent[key] || null,
localContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewResource: this.extUri.joinPath(this.syncPreviewFolder, key),
previewResult,
localChange: previewResult.localChange,
remoteChange: previewResult.remoteChange,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
acceptedResource: this.extUri.joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' })
});
}
}
@@ -351,10 +344,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const snippets = this.parseSnippets(syncData);
const result = [];
for (const snippet of Object.keys(snippets)) {
const resource = joinPath(uri, snippet);
const comparableResource = joinPath(this.snippetsFolder, snippet);
const resource = this.extUri.joinPath(uri, snippet);
const comparableResource = this.extUri.joinPath(this.snippetsFolder, snippet);
const exists = await this.fileService.exists(comparableResource);
result.push({ resource, comparableResource: exists ? comparableResource : joinPath(this.syncPreviewFolder, snippet).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }) });
result.push({ resource, comparableResource: exists ? comparableResource : this.extUri.joinPath(this.syncPreviewFolder, snippet).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }) });
}
return result;
}
@@ -363,9 +356,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
if (this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }))
|| this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }))
|| this.extUri.isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }))) {
return this.resolvePreviewContent(uri);
}
@@ -374,12 +367,12 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
return content;
}
content = await super.resolveContent(dirname(uri));
content = await super.resolveContent(this.extUri.dirname(uri));
if (content) {
const syncData = this.parseSyncData(content);
if (syncData) {
const snippets = this.parseSnippets(syncData);
return snippets[basename(uri)] || null;
return snippets[this.extUri.basename(uri)] || null;
}
}
@@ -402,7 +395,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
const local: IStringDictionary<IFileContent> = {};
for (const resourcePreview of resourcePreviews) {
if (resourcePreview.fileContent) {
local[basename(resourcePreview.localResource!)] = resourcePreview.fileContent;
local[this.extUri.basename(resourcePreview.localResource!)] = resourcePreview.fileContent;
}
}
await this.backupLocal(JSON.stringify(this.toSnippetsContents(local)));
@@ -411,28 +404,28 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
private async updateLocalSnippets(resourcePreviews: ISnippetsAcceptedResourcePreview[], force: boolean): Promise<void> {
for (const { fileContent, acceptResult, localResource, remoteResource, localChange } of resourcePreviews) {
if (localChange !== Change.None) {
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
const resource = joinPath(this.snippetsFolder, key);
const key = remoteResource ? this.extUri.basename(remoteResource) : this.extUri.basename(localResource!);
const resource = this.extUri.joinPath(this.snippetsFolder, key);
// Removed
if (localChange === Change.Deleted) {
this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Deleting snippet...`, this.extUri.basename(resource));
await this.fileService.del(resource);
this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Deleted snippet`, this.extUri.basename(resource));
}
// Added
else if (localChange === Change.Added) {
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Creating snippet...`, this.extUri.basename(resource));
await this.fileService.createFile(resource, VSBuffer.fromString(acceptResult.content!), { overwrite: force });
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Created snippet`, this.extUri.basename(resource));
}
// Updated
else {
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, basename(resource));
this.logService.trace(`${this.syncResourceLogLabel}: Updating snippet...`, this.extUri.basename(resource));
await this.fileService.writeFile(resource, VSBuffer.fromString(acceptResult.content!), force ? undefined : fileContent!);
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, basename(resource));
this.logService.info(`${this.syncResourceLogLabel}: Updated snippet`, this.extUri.basename(resource));
}
}
}
@@ -444,7 +437,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
for (const { acceptResult, localResource, remoteResource, remoteChange } of resourcePreviews) {
if (remoteChange !== Change.None) {
const key = localResource ? basename(localResource) : basename(remoteResource!);
const key = localResource ? this.extUri.basename(localResource) : this.extUri.basename(remoteResource!);
if (remoteChange === Change.Deleted) {
delete newSnippets[key];
} else {
@@ -489,9 +482,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
for (const entry of stat.children || []) {
const resource = entry.resource;
const extension = extname(resource);
const extension = this.extUri.extname(resource);
if (extension === '.json' || extension === '.code-snippets') {
const key = relativePath(this.snippetsFolder, resource)!;
const key = this.extUri.relativePath(this.snippetsFolder, resource)!;
const content = await this.fileService.readFile(resource);
snippets[key] = content;
}
@@ -526,9 +519,9 @@ export class SnippetsInitializer extends AbstractInitializer {
for (const key of Object.keys(remoteSnippets)) {
const content = remoteSnippets[key];
if (content) {
const resource = joinPath(this.environmentService.snippetsHome, key);
const resource = this.extUri.joinPath(this.environmentService.snippetsHome, key);
await this.fileService.createFile(resource, VSBuffer.fromString(content));
this.logService.info('Created snippet', basename(resource));
this.logService.info('Created snippet', this.extUri.basename(resource));
}
}

View File

@@ -1,62 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export interface IStorageKey {
readonly key: string;
readonly version: number;
}
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;
}
export class StorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService {
_serviceBrand: any;
private readonly _storageKeys = new Map<string, IStorageKey>();
get storageKeys(): ReadonlyArray<IStorageKey> { return [...this._storageKeys.values()]; }
private readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
constructor() {
super();
this._register(toDisposable(() => this._storageKeys.clear()));
}
registerStorageKey(storageKey: IStorageKey): void {
if (!this._storageKeys.has(storageKey.key)) {
this._storageKeys.set(storageKey.key, storageKey);
this._onDidChangeStorageKeys.fire(this.storageKeys);
}
}
}

View File

@@ -6,12 +6,12 @@
import { Delayer, disposableTimeout, CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, toDisposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, UserDataAutoSyncError, ISyncTask, IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, UserDataAutoSyncError, ISyncTask, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
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';
@@ -37,27 +37,34 @@ const disableMachineEventuallyKey = 'sync.disableMachineEventually';
const sessionIdKey = 'sync.sessionId';
const storeUrlKey = 'sync.storeUrl';
export class UserDataAutoSyncEnablementService extends Disposable {
interface _IUserDataAutoSyncEnablementService extends IUserDataAutoSyncEnablementService {
canToggleEnablement(): boolean;
setEnablement(enabled: boolean): void;
}
export class UserDataAutoSyncEnablementService extends Disposable implements _IUserDataAutoSyncEnablementService {
_serviceBrand: any;
private _onDidChangeEnablement = new Emitter<boolean>();
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
constructor(
@IStorageService protected readonly storageService: IStorageService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IUserDataSyncStoreManagementService protected readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService
@IStorageService private readonly storageService: IStorageService,
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
) {
super();
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
}
isEnabled(): boolean {
isEnabled(defaultEnablement?: boolean): boolean {
switch (this.environmentService.sync) {
case 'on':
return true;
case 'off':
return false;
default: return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, this.environmentService.enableSyncByDefault); // {{SQL CARBON EDIT}} strict-null-checks move this to a default case
default: return this.storageService.getBoolean(enablementKey, StorageScope.GLOBAL, !!defaultEnablement); // {{SQL CARBON EDIT}} strict-null-checks move this to a default case
}
}
@@ -65,20 +72,29 @@ export class UserDataAutoSyncEnablementService extends Disposable {
return this.userDataSyncStoreManagementService.userDataSyncStore !== undefined && this.environmentService.sync === undefined;
}
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
if (enablementKey === workspaceStorageChangeEvent.key) {
this._onDidChangeEnablement.fire(this.isEnabled());
}
setEnablement(enabled: boolean): void {
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL, StorageTarget.MACHINE);
}
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
if (storageChangeEvent.scope !== StorageScope.GLOBAL) {
return;
}
if (enablementKey === storageChangeEvent.key) {
this._onDidChangeEnablement.fire(this.isEnabled());
return;
}
}
}
export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService implements IUserDataAutoSyncService {
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
_serviceBrand: any;
private readonly userDataAutoSyncEnablementService: _IUserDataAutoSyncEnablementService;
private readonly autoSync = this._register(new MutableDisposable<AutoSync>());
private successiveFailures: number = 0;
private lastSyncTriggerTime: number | undefined = undefined;
@@ -94,14 +110,14 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
}
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);
}
}
constructor(
@IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@@ -109,16 +125,30 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
@IStorageService storageService: IStorageService,
@IEnvironmentService environmentService: IEnvironmentService
@IStorageService private readonly storageService: IStorageService,
@IUserDataAutoSyncEnablementService userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService
) {
super(storageService, environmentService, userDataSyncStoreManagementService);
super();
this.userDataAutoSyncEnablementService = userDataAutoSyncEnablementService as _IUserDataAutoSyncEnablementService;
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
this.lastSyncUrl = this.syncUrl;
this.syncUrl = userDataSyncStoreManagementService.userDataSyncStore?.url;
if (userDataSyncStoreManagementService.userDataSyncStore) {
if (this.isEnabled()) {
if (this.syncUrl) {
this.logService.info('Using settings sync service', this.syncUrl.toString());
this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => {
if (!isEqual(this.syncUrl, userDataSyncStoreManagementService.userDataSyncStore?.url)) {
this.lastSyncUrl = this.syncUrl;
this.syncUrl = userDataSyncStoreManagementService.userDataSyncStore?.url;
if (this.syncUrl) {
this.logService.info('Using settings sync service', this.syncUrl.toString());
}
}
}));
if (this.userDataAutoSyncEnablementService.isEnabled()) {
this.logService.info('Auto Sync is enabled.');
} else {
this.logService.info('Auto Sync is disabled.');
@@ -157,7 +187,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
}
/* log message when auto sync is not disabled by user */
else if (message && this.isEnabled()) {
else if (message && this.userDataAutoSyncEnablementService.isEnabled()) {
this.logService.info(message);
}
}
@@ -167,7 +197,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
protected startAutoSync(): boolean { return true; }
private isAutoSyncEnabled(): { enabled: boolean, message?: string } {
if (!this.isEnabled()) {
if (!this.userDataAutoSyncEnablementService.isEnabled()) {
return { enabled: false, message: 'Auto Sync: Disabled.' };
}
if (!this.userDataSyncAccountService.account) {
@@ -182,7 +212,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
async turnOn(): Promise<void> {
this.stopDisableMachineEventually();
this.lastSyncUrl = this.syncUrl;
this.setEnablement(true);
this.updateEnablement(true);
}
async turnOff(everywhere: boolean, softTurnOffOnError?: boolean, donotRemoveMachine?: boolean): Promise<void> {
@@ -194,7 +224,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
}
// Disable Auto Sync
this.setEnablement(false);
this.updateEnablement(false);
// Reset Session
this.storageService.remove(sessionIdKey, StorageScope.GLOBAL);
@@ -209,17 +239,17 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
} catch (error) {
if (softTurnOffOnError) {
this.logService.error(error);
this.setEnablement(false);
this.updateEnablement(false);
} else {
throw error;
}
}
}
private setEnablement(enabled: boolean): void {
if (this.isEnabled() !== enabled) {
private updateEnablement(enabled: boolean): void {
if (this.userDataAutoSyncEnablementService.isEnabled() !== enabled) {
this.telemetryService.publicLog2<{ enabled: boolean }, AutoSyncEnablementClassification>(enablementKey, { enabled });
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
this.userDataAutoSyncEnablementService.setEnablement(enabled);
this.updateAutoSync();
}
}
@@ -295,7 +325,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
}
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.
@@ -306,7 +336,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
this.stopDisableMachineEventually();
// disable only if sync is disabled
if (!this.isEnabled() && this.userDataSyncAccountService.account) {
if (!this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncAccountService.account) {
await this.userDataSyncMachinesService.removeCurrentMachine();
}
}
@@ -496,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

@@ -120,8 +120,9 @@ export type IAuthenticationProvider = { id: string, scopes: string[] };
export interface IUserDataSyncStore {
readonly url: URI;
readonly defaultUrl: URI;
readonly stableUrl: URI | undefined;
readonly insidersUrl: URI | undefined;
readonly stableUrl: URI;
readonly insidersUrl: URI;
readonly canSwitch: boolean;
readonly authenticationProviders: IAuthenticationProvider[];
}
@@ -157,6 +158,7 @@ export type UserDataSyncStoreType = 'insiders' | 'stable';
export const IUserDataSyncStoreManagementService = createDecorator<IUserDataSyncStoreManagementService>('IUserDataSyncStoreManagementService');
export interface IUserDataSyncStoreManagementService {
readonly _serviceBrand: undefined;
readonly onDidChangeUserDataSyncStore: Event<void>;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
switch(type: UserDataSyncStoreType): Promise<void>;
getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
@@ -218,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',
@@ -250,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);
}
}
@@ -287,6 +291,11 @@ export interface ISyncExtension {
version?: string;
disabled?: boolean;
installed?: boolean;
state?: IStringDictionary<any>;
}
export interface ISyncExtensionWithVersion extends ISyncExtension {
version: string;
}
export interface IStorageValue {
@@ -349,6 +358,10 @@ export interface ISyncResourcePreview {
readonly resourcePreviews: IResourcePreview[];
}
export interface IUserDataInitializer {
initialize(userData: IUserData): Promise<void>;
}
export interface IUserDataSynchroniser {
readonly resource: SyncResource;
@@ -454,13 +467,18 @@ export interface IUserDataSyncService {
getMachineId(resource: SyncResource, syncResourceHandle: ISyncResourceHandle): Promise<string | undefined>;
}
export const IUserDataAutoSyncEnablementService = createDecorator<IUserDataAutoSyncEnablementService>('IUserDataAutoSyncEnablementService');
export interface IUserDataAutoSyncEnablementService {
_serviceBrand: any;
readonly onDidChangeEnablement: Event<boolean>;
isEnabled(): boolean;
canToggleEnablement(): boolean;
}
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
export interface IUserDataAutoSyncService {
_serviceBrand: any;
readonly onError: Event<UserDataSyncError>;
readonly onDidChangeEnablement: Event<boolean>;
isEnabled(): boolean;
canToggleEnablement(): boolean;
turnOn(): Promise<void>;
turnOff(everywhere: boolean): Promise<void>;
triggerSync(sources: string[], hasToLimitSync: boolean, disableCache: boolean): Promise<void>;

View File

@@ -4,13 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { IServerChannel, IChannel, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IUserDataSyncService, IUserDataSyncUtilService, IUserDataAutoSyncService, IManualSyncTask, IUserDataManifest, IUserDataSyncStoreManagementService, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
import { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
@@ -175,54 +173,6 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
}
export class StorageKeysSyncRegistryChannel implements IServerChannel {
constructor(private readonly service: IStorageKeysSyncRegistryService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case '_getInitialData': return Promise.resolve(this.service.storageKeys);
case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0]));
}
throw new Error('Invalid call');
}
}
export class StorageKeysSyncRegistryChannelClient extends Disposable implements IStorageKeysSyncRegistryService {
declare readonly _serviceBrand: undefined;
private _storageKeys: ReadonlyArray<IStorageKey> = [];
get storageKeys(): ReadonlyArray<IStorageKey> { return this._storageKeys; }
private readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
constructor(private readonly channel: IChannel) {
super();
this.channel.call<IStorageKey[]>('_getInitialData').then(storageKeys => {
this.updateStorageKeys(storageKeys);
this._register(this.channel.listen<ReadonlyArray<IStorageKey>>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys)));
});
}
private async updateStorageKeys(storageKeys: ReadonlyArray<IStorageKey>): Promise<void> {
this._storageKeys = storageKeys;
this._onDidChangeStorageKeys.fire(this.storageKeys);
}
registerStorageKey(storageKey: IStorageKey): void {
this.channel.call('registerStorageKey', [storageKey]);
}
}
export class UserDataSyncMachinesServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncMachinesService) { }
@@ -271,6 +221,9 @@ export class UserDataSyncStoreManagementServiceChannel implements IServerChannel
constructor(private readonly service: IUserDataSyncStoreManagementService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeUserDataSyncStore': return this.service.onDidChangeUserDataSyncStore;
}
throw new Error(`Event not found: ${event}`);
}

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,18 +39,17 @@ 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;
@@ -138,6 +139,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async stop(): Promise<void> {
if (cancellablePromise) {
cancellablePromise.cancel();
}
if (that.status !== SyncStatus.Idle) {
return that.stop();
}
}
@@ -154,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);
@@ -200,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);
@@ -363,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);
}
}
@@ -388,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';
@@ -36,7 +37,10 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa
_serviceBrand: any;
readonly userDataSyncStore: UserDataSyncStore | undefined;
private readonly _onDidChangeUserDataSyncStore = this._register(new Emitter<void>());
readonly onDidChangeUserDataSyncStore = this._onDidChangeUserDataSyncStore.event;
private _userDataSyncStore: UserDataSyncStore | undefined;
get userDataSyncStore(): UserDataSyncStore | undefined { return this._userDataSyncStore; }
constructor(
@IProductService protected readonly productService: IProductService,
@@ -44,10 +48,17 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa
@IStorageService protected readonly storageService: IStorageService,
) {
super();
this.userDataSyncStore = this.toUserDataSyncStore(productService[CONFIGURATION_SYNC_STORE_KEY], configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY));
this.updateUserDataSyncStore();
}
protected updateUserDataSyncStore(): void {
this._userDataSyncStore = this.toUserDataSyncStore(this.productService[CONFIGURATION_SYNC_STORE_KEY], this.configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY));
this._onDidChangeUserDataSyncStore.fire();
}
protected toUserDataSyncStore(productStore: ConfigurationSyncStore | undefined, configuredStore?: ConfigurationSyncStore): UserDataSyncStore | undefined {
// Web overrides
productStore = isWeb && productStore?.web ? { ...productStore, ...productStore.web } : productStore;
const value: Partial<ConfigurationSyncStore> = { ...(productStore || {}), ...(configuredStore || {}) };
if (value
&& isString(value.url)
@@ -55,17 +66,20 @@ export abstract class AbstractUserDataSyncStoreManagementService extends Disposa
&& Object.keys(value.authenticationProviders).every(authenticationProviderId => isArray(value!.authenticationProviders![authenticationProviderId].scopes))
) {
const syncStore = value as ConfigurationSyncStore;
const type: UserDataSyncStoreType | undefined = this.storageService.get(SYNC_SERVICE_URL_TYPE, StorageScope.GLOBAL) as UserDataSyncStoreType | undefined;
const url = configuredStore?.url
|| (type === 'insiders' ? syncStore.insidersUrl : type === 'stable' ? syncStore.stableUrl : undefined)
|| syncStore.url;
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);
return {
url: URI.parse(url),
type,
defaultType: syncStore.url === syncStore.insidersUrl ? 'insiders' : syncStore.url === syncStore.stableUrl ? 'stable' : undefined,
defaultUrl: URI.parse(syncStore.url),
stableUrl: syncStore.stableUrl ? URI.parse(syncStore.stableUrl) : undefined,
insidersUrl: syncStore.insidersUrl ? URI.parse(syncStore.insidersUrl) : undefined,
stableUrl: URI.parse(syncStore.stableUrl),
insidersUrl: URI.parse(syncStore.insidersUrl),
canSwitch: !!syncStore.canSwitch && !configuredStore?.url,
authenticationProviders: Object.keys(syncStore.authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
result.push({ id, scopes: syncStore!.authenticationProviders[id].scopes });
return result;
@@ -88,7 +102,6 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@IStorageService storageService: IStorageService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
) {
super(productService, configurationService, storageService);
@@ -99,23 +112,20 @@ 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);
}
if (this.userDataSyncStore) {
logService.info('Using settings sync service', this.userDataSyncStore.url.toString());
}
}
async switch(type: UserDataSyncStoreType): Promise<void> {
if (type !== this.userDataSyncStore?.type) {
if (type === this.userDataSyncStore?.defaultType) {
if (this.userDataSyncStore?.canSwitch && type !== this.userDataSyncStore.type) {
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();
}
}
@@ -126,7 +136,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
export class UserDataSyncStoreClient extends Disposable implements IUserDataSyncStoreClient {
private readonly userDataSyncStoreUrl: URI | undefined;
private userDataSyncStoreUrl: URI | undefined;
private authToken: { token: string, type: string } | undefined;
private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>;
@@ -153,7 +163,7 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
@IStorageService private readonly storageService: IStorageService,
) {
super();
this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
this.updateUserDataSyncStoreUrl(userDataSyncStoreUrl);
this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService)
.then(uuid => {
const headers: IHeaders = {
@@ -176,6 +186,10 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync
this.authToken = { token, type };
}
protected updateUserDataSyncStoreUrl(userDataSyncStoreUrl: URI | undefined): void {
this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
}
private initDonotMakeRequestsUntil(): void {
const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
@@ -194,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);
@@ -212,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 */ }));
@@ -227,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;
}
@@ -240,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> {
@@ -256,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.
@@ -265,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 };
@@ -283,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;
}
@@ -301,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);
@@ -318,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;
@@ -332,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();
@@ -343,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);
@@ -364,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);
@@ -389,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;
@@ -435,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;
@@ -461,6 +477,7 @@ export class UserDataSyncStoreService extends UserDataSyncStoreClient implements
@IStorageService storageService: IStorageService,
) {
super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService);
this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.updateUserDataSyncStoreUrl(userDataSyncStoreManagementService.userDataSyncStore?.url)));
}
}
@@ -476,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

@@ -2,15 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSync';
//
import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, IUserDataSyncStoreManagementService, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@@ -20,19 +19,19 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@IUserDataSyncService userDataSyncService: IUserDataSyncService,
@IElectronService electronService: IElectronService,
@INativeHostService nativeHostService: INativeHostService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IUserDataSyncAccountService authTokenService: IUserDataSyncAccountService,
@ITelemetryService telemetryService: ITelemetryService,
@IUserDataSyncMachinesService userDataSyncMachinesService: IUserDataSyncMachinesService,
@IStorageService storageService: IStorageService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataAutoSyncEnablementService userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
) {
super(userDataSyncStoreManagementService, userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, userDataSyncMachinesService, storageService, environmentService);
super(userDataSyncStoreManagementService, userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, userDataSyncMachinesService, storageService, userDataAutoSyncEnablementService);
this._register(Event.debounce<string, string[]>(Event.any<string>(
Event.map(electronService.onWindowFocus, () => 'windowFocus'),
Event.map(electronService.onWindowOpen, () => 'windowOpen'),
Event.map(nativeHostService.onDidFocusWindow, () => 'windowFocus'),
Event.map(nativeHostService.onDidOpenWindow, () => 'windowOpen'),
), (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, true, false)));
}

View File

@@ -4,16 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { ISyncExtension, ISyncExtensionWithVersion } from 'vs/platform/userDataSync/common/userDataSync';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
suite('ExtensionsMerge', () => {
test('merge returns local extension if remote does not exist', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, null, null, [], []);
@@ -25,14 +25,14 @@ suite('ExtensionsMerge', () => {
});
test('merge returns local extension if remote does not exist with ignored extensions', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, null, null, [], ['a']);
@@ -44,14 +44,14 @@ suite('ExtensionsMerge', () => {
});
test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, null, null, [], ['A']);
@@ -63,18 +63,18 @@ suite('ExtensionsMerge', () => {
});
test('merge returns local extension if remote does not exist with skipped extensions', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, null, null, skippedExtension, []);
@@ -86,17 +86,17 @@ suite('ExtensionsMerge', () => {
});
test('merge returns local extension if remote does not exist with skipped and ignored extensions', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, null, null, skippedExtension, ['a']);
@@ -108,47 +108,47 @@ suite('ExtensionsMerge', () => {
});
test('merge local and remote extensions when there is no base', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when there is no base and with ignored extensions', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], ['a']);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -159,18 +159,18 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -181,21 +181,21 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true }]);
assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true, installed: true, version: '1.0.0' }]);
assert.equal(actual.remote, null);
});
@@ -204,18 +204,18 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -226,20 +226,20 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true }, { identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' }, { identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -250,20 +250,20 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']);
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -274,13 +274,13 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
@@ -296,14 +296,14 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, disabled: true, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, disabled: true, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
@@ -319,13 +319,13 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
@@ -334,7 +334,7 @@ suite('ExtensionsMerge', () => {
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, [
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
]);
});
@@ -346,18 +346,18 @@ suite('ExtensionsMerge', () => {
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
@@ -376,17 +376,17 @@ suite('ExtensionsMerge', () => {
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']);
@@ -402,25 +402,25 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -431,20 +431,20 @@ suite('ExtensionsMerge', () => {
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']);
@@ -463,24 +463,24 @@ suite('ExtensionsMerge', () => {
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -494,19 +494,19 @@ suite('ExtensionsMerge', () => {
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'e', uuid: 'e' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'e', uuid: 'e' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']);
@@ -518,37 +518,37 @@ suite('ExtensionsMerge', () => {
});
test('merge when remote extension has no uuid and different extension id case', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'A' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'A' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'A', uuid: 'a' }, installed: true },
{ identifier: { id: 'd', uuid: 'd' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' }, installed: true },
{ identifier: { id: 'c', uuid: 'c' }, installed: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'A', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'c', uuid: 'c' }, installed: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, installed: true, version: '1.0.0' }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge when remote extension is not an installed extension', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
{ identifier: { id: 'b', uuid: 'b' } },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
@@ -560,11 +560,11 @@ suite('ExtensionsMerge', () => {
});
test('merge when remote extension is not an installed extension but is an installed extension locally', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
@@ -576,12 +576,12 @@ suite('ExtensionsMerge', () => {
});
test('merge when an extension is not an installed extension remotely and does not exist locally', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
{ identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []);
@@ -593,14 +593,14 @@ suite('ExtensionsMerge', () => {
});
test('merge when an extension is an installed extension remotely but not locally and updated locally', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, disabled: true },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, disabled: true, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, remoteExtensions, [], []);
@@ -612,11 +612,11 @@ suite('ExtensionsMerge', () => {
});
test('merge when an extension is an installed extension remotely but not locally and updated remotely', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, installed: true, disabled: true, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, localExtensions, [], []);
@@ -628,15 +628,15 @@ suite('ExtensionsMerge', () => {
});
test('merge not installed extensions', () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
const localExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' } },
const remoteExtensions: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'a', uuid: 'a' } },
const expected: ISyncExtensionWithVersion[] = [
{ identifier: { id: 'b', uuid: 'b' }, version: '1.0.0' },
{ identifier: { id: 'a', uuid: 'a' }, version: '1.0.0' },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);

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

@@ -6,7 +6,7 @@
import { IRequestService } from 'vs/platform/request/common/request';
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource, ServerResource, IUserDataSyncStoreManagementService, registerConfiguration } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataManifest, ALL_SYNC_RESOURCES, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncResourceEnablementService, IUserDataSyncService, getDefaultIgnoredSettings, IUserDataSyncBackupStoreService, SyncResource, ServerResource, IUserDataSyncStoreManagementService, registerConfiguration, IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
import { generateUuid } from 'vs/base/common/uuid';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
@@ -36,8 +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 {
@@ -69,6 +71,9 @@ export class UserDataSyncClient extends Disposable {
_serviceBrand: undefined, ...product, ...{
'configurationSync.store': {
url: this.testServer.url,
stableUrl: this.testServer.url,
insidersUrl: this.testServer.url,
canSwitch: false,
authenticationProviders: { 'test': { scopes: [] } }
}
}
@@ -99,9 +104,10 @@ 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 []; },
onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event,
@@ -112,6 +118,7 @@ export class UserDataSyncClient extends Disposable {
async getCompatibleExtension() { return null; }
});
this.instantiationService.stub(IUserDataAutoSyncEnablementService, this.instantiationService.createInstance(UserDataAutoSyncEnablementService));
this.instantiationService.stub(IUserDataSyncService, this.instantiationService.createInstance(UserDataSyncService));
if (!empty) {

View File

@@ -39,6 +39,9 @@ suite('UserDataSyncStoreManagementService', () => {
const configuredStore: ConfigurationSyncStore = {
url: 'http://configureHost:3000',
stableUrl: 'http://configureHost:3000',
insidersUrl: 'http://configureHost:3000',
canSwitch: false,
authenticationProviders: { 'configuredAuthProvider': { scopes: [] } }
};
await client.instantiationService.get(IFileService).writeFile(client.instantiationService.get(IEnvironmentService).settingsResource, VSBuffer.fromString(JSON.stringify({
@@ -49,8 +52,9 @@ suite('UserDataSyncStoreManagementService', () => {
const expected: IUserDataSyncStore = {
url: URI.parse('http://configureHost:3000'),
defaultUrl: URI.parse('http://configureHost:3000'),
stableUrl: undefined,
insidersUrl: undefined,
stableUrl: URI.parse('http://configureHost:3000'),
insidersUrl: URI.parse('http://configureHost:3000'),
canSwitch: false,
authenticationProviders: [{ id: 'configuredAuthProvider', scopes: [] }]
};
@@ -460,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);
@@ -474,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);