mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
97
src/vs/platform/userDataSync/common/extensionsStorageSync.ts
Normal file
97
src/vs/platform/userDataSync/common/extensionsStorageSync.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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> } {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
95
src/vs/platform/userDataSync/common/ignoredExtensions.ts
Normal file
95
src/vs/platform/userDataSync/common/ignoredExtensions.ts
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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 || '{}');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IUserDataSyncStoreService, IUserData, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -103,7 +103,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData
|
||||
machine.name = name;
|
||||
await this.writeMachinesData(machineData);
|
||||
if (machineData.machines.some(({ id }) => id === currentMachineId)) {
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL);
|
||||
this.storageService.store(currentMachineNameKey, name, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IUserDataSyncResourceEnablementService, ALL_SYNC_RESOURCES, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
type SyncEnablementClassification = {
|
||||
@@ -28,7 +28,7 @@ export class UserDataSyncResourceEnablementService extends Disposable implements
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
isResourceEnabled(resource: SyncResource): boolean {
|
||||
@@ -39,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)]));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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, [], []);
|
||||
|
||||
@@ -9,11 +9,11 @@ import { NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
suite('GlobalStateMerge', () => {
|
||||
|
||||
test('merge when local and remote are same with one value', async () => {
|
||||
test('merge when local and remote are same with one value and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -21,11 +21,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
test('merge when local and remote are same with multiple entries and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -33,11 +33,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
test('merge when local and remote are same with multiple entries in different order and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -50,7 +50,7 @@ suite('GlobalStateMerge', () => {
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const base = { 'b': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -58,11 +58,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
test('merge when a new entry is added to remote and local has not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -70,11 +70,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
test('merge when multiple new entries are added to remote and local is not synced yet', async () => {
|
||||
const local = {};
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -86,7 +86,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -98,7 +98,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -110,7 +110,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = {};
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -122,7 +122,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
@@ -134,7 +134,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'd' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } });
|
||||
@@ -142,11 +142,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when new entries are added to local', async () => {
|
||||
test('merge when new entries are added to local and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -158,7 +158,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -170,7 +170,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -182,7 +182,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -194,7 +194,7 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'd' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, remote, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -202,11 +202,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
test('merge when local and remote with one entry but different value and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
@@ -219,12 +219,12 @@ suite('GlobalStateMerge', () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, ['b']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' }, 'b': { version: 1, value: 'd' } });
|
||||
});
|
||||
|
||||
test('merge with single entry and local is empty', async () => {
|
||||
@@ -232,32 +232,32 @@ suite('GlobalStateMerge', () => {
|
||||
const local = {};
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local and remote has moved forwareded with conflicts', async () => {
|
||||
test('merge when local and remote has moved forward with conflicts', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' } };
|
||||
const local = { 'a': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but not a registered key', async () => {
|
||||
test('merge when a new entry is added to remote but scoped to machine locally and local is not synced yet', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, null, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -265,23 +265,11 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but different version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated to remote but not a registered key', async () => {
|
||||
test('merge when an entry is updated to remote but scoped to machine locally', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [], [], new NullLogService());
|
||||
const actual = merge(local, remote, local, { machine: ['a'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
@@ -289,92 +277,43 @@ suite('GlobalStateMerge', () => {
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is updated to remote but different version', async () => {
|
||||
test('merge when a local value is removed and scoped to machine locally', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local moved forwared by changing a key to machine scope', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, { machine: ['b'], unregistered: [] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge should not remove remote keys if not registered', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
const actual = merge(local, remote, base, { machine: [], unregistered: ['c'] }, new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with lower version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'c' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with higher version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 2, value: 'c' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed but not registered', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with lower version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with higher version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a local value is not yet registered', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
assert.deepEqual(actual.remote, { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -10,10 +10,9 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
|
||||
suite('GlobalStateSync', () => {
|
||||
@@ -28,17 +27,11 @@ suite('GlobalStateSync', () => {
|
||||
setup(async () => {
|
||||
testClient = disposableStore.add(new UserDataSyncClient(server));
|
||||
await testClient.setUp(true);
|
||||
let storageKeysSyncRegistryService = testClient.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser;
|
||||
disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear()));
|
||||
|
||||
client2 = disposableStore.add(new UserDataSyncClient(server));
|
||||
await client2.setUp(true);
|
||||
storageKeysSyncRegistryService = client2.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
});
|
||||
|
||||
teardown(() => disposableStore.clear());
|
||||
@@ -72,7 +65,7 @@ suite('GlobalStateSync', () => {
|
||||
|
||||
test('when global state is created after first sync', async () => {
|
||||
await testObject.sync(await testClient.manifest());
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
|
||||
let lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const manifest = await testClient.manifest();
|
||||
@@ -91,7 +84,8 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync - outgoing to server (no state)', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
updateMachineStorage('b', 'value1', testClient);
|
||||
await updateLocale(testClient);
|
||||
|
||||
await testObject.sync(await testClient.manifest());
|
||||
@@ -105,7 +99,7 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync - incoming from server (no state)', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await updateLocale(client2);
|
||||
await client2.sync();
|
||||
|
||||
@@ -118,10 +112,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync when storage exists', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -136,10 +130,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('first time sync when storage exists - has conflicts', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateUserStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('a', 'value2', client2);
|
||||
updateUserStorage('a', 'value2', client2);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
@@ -154,10 +148,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync adding a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -172,10 +166,10 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync updating a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
updateStorage('a', 'value2', testClient);
|
||||
updateUserStorage('a', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
@@ -189,8 +183,8 @@ suite('GlobalStateSync', () => {
|
||||
});
|
||||
|
||||
test('sync removing a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateStorage('b', 'value2', testClient);
|
||||
updateUserStorage('a', 'value1', testClient);
|
||||
updateUserStorage('b', 'value2', testClient);
|
||||
await testObject.sync(await testClient.manifest());
|
||||
|
||||
removeStorage('b', testClient);
|
||||
@@ -218,9 +212,14 @@ suite('GlobalStateSync', () => {
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' })));
|
||||
}
|
||||
|
||||
function updateStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
function updateUserStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.store(key, value, StorageScope.GLOBAL);
|
||||
storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
function updateMachineStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.store(key, value, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
function removeStorage(key: string, client: UserDataSyncClient): void {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user