Merge from vscode 0a7364f00514c46c9caceece15e1f82f82e3712f

This commit is contained in:
ADS Merger
2020-07-22 03:06:57 +00:00
parent 53ec7585a9
commit 1b7b54ce14
229 changed files with 5099 additions and 3188 deletions

View File

@@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry';
import { IConfigurationRegistry, Extensions, OVERRIDE_PROPERTY_PATTERN, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
export const IConfigurationService = createDecorator<IConfigurationService>('configurationService');
@@ -354,10 +354,6 @@ export function getDefaultValues(): any {
return valueTreeRoot;
}
export function overrideIdentifierFromKey(key: string): string {
return key.substring(1, key.length - 1);
}
export function keyFromOverrideIdentifier(overrideIdentifier: string): string {
return `[${overrideIdentifier}]`;
}

View File

@@ -9,8 +9,8 @@ import * as arrays from 'vs/base/common/arrays';
import * as types from 'vs/base/common/types';
import * as objects from 'vs/base/common/objects';
import { URI, UriComponents } from 'vs/base/common/uri';
import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, overrideIdentifierFromKey, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { OVERRIDE_PROPERTY_PATTERN, ConfigurationScope, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configurationRegistry';
import { IOverrides, addToValueTree, toValuesTree, IConfigurationModel, getConfigurationValue, IConfigurationOverrides, IConfigurationData, getDefaultValues, getConfigurationKeys, removeFromValueTree, toOverrides, IConfigurationValue, ConfigurationTarget, compare, IConfigurationChangeEvent, IConfigurationChange } from 'vs/platform/configuration/common/configuration';
import { Workspace } from 'vs/platform/workspace/common/workspace';
import { Registry } from 'vs/platform/registry/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';

View File

@@ -9,7 +9,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Registry } from 'vs/platform/registry/common/platform';
import * as types from 'vs/base/common/types';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IStringDictionary } from 'vs/base/common/collections';
export const Extensions = {
Configuration: 'base.contributions.configuration'
@@ -35,12 +35,12 @@ export interface IConfigurationRegistry {
/**
* Register multiple default configurations to the registry.
*/
registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
registerDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void;
/**
* Deregister multiple default configurations from the registry.
*/
deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void;
deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void;
/**
* Signal that the schema of a configuration setting has changes. It is currently only supported to change enumeration values.
@@ -131,12 +131,6 @@ export interface IConfigurationNode {
extensionInfo?: IConfigurationExtensionInfo;
}
export interface IDefaultConfigurationExtension {
id: ExtensionIdentifier;
name: string;
defaults: { [key: string]: {} };
}
type SettingProperties = { [key: string]: any };
export const allSettings: { properties: SettingProperties, patternProperties: SettingProperties } = { properties: {}, patternProperties: {} };
@@ -152,7 +146,8 @@ const contributionRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensio
class ConfigurationRegistry implements IConfigurationRegistry {
private readonly defaultOverridesConfigurationNode: IConfigurationNode;
private readonly defaultValues: IStringDictionary<any>;
private readonly defaultLanguageConfigurationOverridesNode: IConfigurationNode;
private readonly configurationContributors: IConfigurationNode[];
private readonly configurationProperties: { [qualifiedKey: string]: IJSONSchema };
private readonly excludedConfigurationProperties: { [qualifiedKey: string]: IJSONSchema };
@@ -166,12 +161,13 @@ class ConfigurationRegistry implements IConfigurationRegistry {
readonly onDidUpdateConfiguration: Event<string[]> = this._onDidUpdateConfiguration.event;
constructor() {
this.defaultOverridesConfigurationNode = {
this.defaultValues = {};
this.defaultLanguageConfigurationOverridesNode = {
id: 'defaultOverrides',
title: nls.localize('defaultConfigurations.title', "Default Configuration Overrides"),
title: nls.localize('defaultLanguageConfigurationOverrides.title', "Default Language Configuration Overrides"),
properties: {}
};
this.configurationContributors = [this.defaultOverridesConfigurationNode];
this.configurationContributors = [this.defaultLanguageConfigurationOverridesNode];
this.resourceLanguageSettingsSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true };
this.configurationProperties = {};
this.excludedConfigurationProperties = {};
@@ -202,29 +198,8 @@ class ConfigurationRegistry implements IConfigurationRegistry {
if (configuration.properties) {
for (const key in configuration.properties) {
properties.push(key);
delete this.configurationProperties[key];
// Delete from schema
delete allSettings.properties[key];
switch (configuration.properties[key].scope) {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.MACHINE:
delete machineSettings.properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
delete machineOverridableSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
delete resourceSettings.properties[key];
break;
}
this.removeFromSchema(key, configuration.properties[key]);
}
}
if (configuration.allOf) {
@@ -244,41 +219,60 @@ class ConfigurationRegistry implements IConfigurationRegistry {
this._onDidUpdateConfiguration.fire(properties);
}
public registerDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
public registerDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void {
const properties: string[] = [];
const overrideIdentifiers: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
const defaultValue = defaultConfiguration.defaults[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key) && typeof defaultValue === 'object') {
const propertySchema: IConfigurationPropertySchema = {
for (const key in defaultConfiguration) {
properties.push(key);
this.defaultValues[key] = defaultConfiguration[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
const property: IConfigurationPropertySchema = {
type: 'object',
default: defaultValue,
description: nls.localize('overrideSettings.description', "Configure editor settings to be overridden for {0} language.", key),
default: this.defaultValues[key],
description: nls.localize('defaultLanguageConfiguration.description', "Configure settings to be overridden for {0} language.", key),
$ref: resourceLanguageSettingsSchemaId
};
allSettings.properties[key] = propertySchema;
this.defaultOverridesConfigurationNode.properties![key] = propertySchema;
this.configurationProperties[key] = propertySchema;
properties.push(key);
overrideIdentifiers.push(overrideIdentifierFromKey(key));
this.configurationProperties[key] = property;
this.defaultLanguageConfigurationOverridesNode.properties![key] = property;
} else {
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
this.updateSchema(key, property);
}
}
}
}
this.registerOverrideIdentifiers(overrideIdentifiers);
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
public deregisterDefaultConfigurations(defaultConfigurations: IDefaultConfigurationExtension[]): void {
public deregisterDefaultConfigurations(defaultConfigurations: IStringDictionary<any>[]): void {
const properties: string[] = [];
for (const defaultConfiguration of defaultConfigurations) {
for (const key in defaultConfiguration.defaults) {
for (const key in defaultConfiguration) {
properties.push(key);
delete allSettings.properties[key];
delete this.defaultOverridesConfigurationNode.properties![key];
delete this.configurationProperties[key];
delete this.defaultValues[key];
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
delete this.configurationProperties[key];
delete this.defaultLanguageConfigurationOverridesNode.properties![key];
} else {
const property = this.configurationProperties[key];
if (property) {
this.updatePropertyDefaultValue(key, property);
this.updateSchema(key, property);
}
}
}
}
this.updateOverridePropertyPatternKey();
this._onDidSchemaChange.fire();
this._onDidUpdateConfiguration.fire(properties);
}
@@ -291,7 +285,6 @@ class ConfigurationRegistry implements IConfigurationRegistry {
for (const overrideIdentifier of overrideIdentifiers) {
this.overrideIdentifiers.add(overrideIdentifier);
}
this.updateOverridePropertyPatternKey();
}
@@ -305,12 +298,13 @@ class ConfigurationRegistry implements IConfigurationRegistry {
delete properties[key];
continue;
}
// fill in default values
let property = properties[key];
let defaultValue = property.default;
if (types.isUndefined(defaultValue)) {
property.default = getDefaultValue(property.type);
}
const property = properties[key];
// update default value
this.updatePropertyDefaultValue(key, property);
// update scope
if (OVERRIDE_PROPERTY_PATTERN.test(key)) {
property.scope = undefined; // No scope for overridable properties `[${identifier}]`
} else {
@@ -361,28 +355,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
let properties = configuration.properties;
if (properties) {
for (const key in properties) {
allSettings.properties[key] = properties[key];
switch (properties[key].scope) {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = properties[key];
break;
case ConfigurationScope.MACHINE:
machineSettings.properties[key] = properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
machineOverridableSettings.properties[key] = properties[key];
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = properties[key];
break;
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = properties[key];
break;
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
resourceSettings.properties[key] = properties[key];
this.resourceLanguageSettingsSchema.properties![key] = properties[key];
break;
}
this.updateSchema(key, properties[key]);
}
}
let subNodes = configuration.allOf;
@@ -393,6 +366,53 @@ class ConfigurationRegistry implements IConfigurationRegistry {
register(configuration);
}
private updateSchema(key: string, property: IConfigurationPropertySchema): void {
allSettings.properties[key] = property;
switch (property.scope) {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = property;
break;
case ConfigurationScope.MACHINE:
machineSettings.properties[key] = property;
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
machineOverridableSettings.properties[key] = property;
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = property;
break;
case ConfigurationScope.RESOURCE:
resourceSettings.properties[key] = property;
break;
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
resourceSettings.properties[key] = property;
this.resourceLanguageSettingsSchema.properties![key] = property;
break;
}
}
private removeFromSchema(key: string, property: IConfigurationPropertySchema): void {
delete allSettings.properties[key];
switch (property.scope) {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.MACHINE:
delete machineSettings.properties[key];
break;
case ConfigurationScope.MACHINE_OVERRIDABLE:
delete machineOverridableSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
case ConfigurationScope.RESOURCE:
case ConfigurationScope.LANGUAGE_OVERRIDABLE:
delete resourceSettings.properties[key];
break;
}
}
private updateOverridePropertyPatternKey(): void {
for (const overrideIdentifier of this.overrideIdentifiers.values()) {
const overrideIdentifierProperty = `[${overrideIdentifier}]`;
@@ -401,8 +421,8 @@ class ConfigurationRegistry implements IConfigurationRegistry {
description: nls.localize('overrideSettings.defaultDescription', "Configure editor settings to be overridden for a language."),
errorMessage: nls.localize('overrideSettings.errorMessage', "This setting does not support per-language configuration."),
$ref: resourceLanguageSettingsSchemaId,
default: this.defaultOverridesConfigurationNode.properties![overrideIdentifierProperty]?.default
};
this.updatePropertyDefaultValue(overrideIdentifierProperty, resourceLanguagePropertiesSchema);
allSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
applicationSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
machineSettings.properties[overrideIdentifierProperty] = resourceLanguagePropertiesSchema;
@@ -412,11 +432,26 @@ class ConfigurationRegistry implements IConfigurationRegistry {
}
this._onDidSchemaChange.fire();
}
private updatePropertyDefaultValue(key: string, property: IConfigurationPropertySchema): void {
let defaultValue = this.defaultValues[key];
if (types.isUndefined(defaultValue)) {
defaultValue = property.default;
}
if (types.isUndefined(defaultValue)) {
defaultValue = getDefaultValue(property.type);
}
property.default = defaultValue;
}
}
const OVERRIDE_PROPERTY = '\\[.*\\]$';
export const OVERRIDE_PROPERTY_PATTERN = new RegExp(OVERRIDE_PROPERTY);
export function overrideIdentifierFromKey(key: string): string {
return key.substring(1, key.length - 1);
}
export function getDefaultValue(type: string | string[] | undefined): any {
const t = Array.isArray(type) ? (<string[]>type)[0] : <string>type;
switch (t) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
@@ -64,6 +64,10 @@ export interface ICommonElectronService {
updateTouchBar(items: ISerializableCommandAction[][]): Promise<void>;
moveItemToTrash(fullPath: string, deleteOnFail?: boolean): Promise<boolean>;
isAdmin(): Promise<boolean>;
getTotalMem(): Promise<number>;
// Process
killProcess(pid: number, code: string): Promise<void>;
// clipboard
readClipboardText(type?: 'selection' | 'clipboard'): Promise<string>;
@@ -94,7 +98,6 @@ export interface ICommonElectronService {
// Development
openDevTools(options?: OpenDevToolsOptions): Promise<void>;
toggleDevTools(): Promise<void>;
startCrashReporter(options: CrashReporterStartOptions): Promise<void>;
sendInputEvent(event: MouseInputEvent): Promise<void>;
// Connectivity

View File

@@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
import { OpenContext } from 'vs/platform/windows/node/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
@@ -20,9 +20,9 @@ import { dirExists } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { totalmem } from 'os';
export interface IElectronMainService extends AddFirstParameterToFunctions<ICommonElectronService, Promise<unknown> /* only methods, not events */, number | undefined /* window ID */> { }
@@ -37,8 +37,7 @@ export class ElectronMainService implements IElectronMainService {
@IDialogMainService private readonly dialogMainService: IDialogMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILogService private readonly logService: ILogService
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
}
@@ -313,6 +312,19 @@ export class ElectronMainService implements IElectronMainService {
return isAdmin;
}
async getTotalMem(): Promise<number> {
return totalmem();
}
//#endregion
//#region Process
async killProcess(windowId: number | undefined, pid: number, code: string): Promise<void> {
process.kill(pid, code);
}
//#endregion
@@ -465,12 +477,6 @@ export class ElectronMainService implements IElectronMainService {
}
}
async startCrashReporter(windowId: number | undefined, options: CrashReporterStartOptions): Promise<void> {
this.logService.trace('ElectronMainService#crashReporter', JSON.stringify(options));
crashReporter.start(options);
}
async sendInputEvent(windowId: number | undefined, event: MouseInputEvent): Promise<void> {
const window = this.windowById(windowId);
if (window && (event.type === 'mouseDown' || event.type === 'mouseUp')) {

View File

@@ -64,18 +64,20 @@ export interface ParsedArgs {
'disable-updates'?: boolean;
'disable-crash-reporter'?: boolean;
'crash-reporter-directory'?: string;
'crash-reporter-id'?: string;
'skip-add-to-recently-opened'?: boolean;
'max-memory'?: string;
'file-write'?: boolean;
'file-chmod'?: boolean;
'driver'?: string;
'driver-verbose'?: boolean;
remote?: string;
'remote'?: string;
'disable-user-env-probe'?: boolean;
'force'?: boolean;
'do-not-sync'?: boolean;
'force-user-env'?: boolean;
'sync'?: 'on' | 'off';
'__sandbox'?: boolean;
// {{SQL CARBON EDIT}} Start
aad?: boolean;
@@ -190,6 +192,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'disable-updates': { type: 'boolean' },
'disable-crash-reporter': { type: 'boolean' },
'crash-reporter-directory': { type: 'string' },
'crash-reporter-id': { type: 'string' },
'disable-user-env-probe': { type: 'boolean' },
'skip-add-to-recently-opened': { type: 'boolean' },
'unity-launch': { type: 'boolean' },
@@ -204,6 +207,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'trace-options': { type: 'string' },
'force-user-env': { type: 'boolean' },
'open-devtools': { type: 'boolean' },
'__sandbox': { type: 'boolean' },
// {{SQL CARBON EDIT}} Start
'command': { type: 'string', alias: 'c', cat: 'o', args: 'command-name', description: localize('commandParameter', 'Name of command to run') },

View File

@@ -43,6 +43,8 @@ export interface INativeEnvironmentService extends IEnvironmentService {
driverVerbose: boolean;
disableUpdates: boolean;
sandbox: boolean;
}
export class EnvironmentService implements INativeEnvironmentService {
@@ -254,7 +256,7 @@ export class EnvironmentService implements INativeEnvironmentService {
get serviceMachineIdResource(): URI { return resources.joinPath(URI.file(this.userDataPath), 'machineid'); }
get disableUpdates(): boolean { return !!this._args['disable-updates']; }
get disableCrashReporter(): boolean { return !!this._args['disable-crash-reporter']; }
get crashReporterId(): string | undefined { return this._args['crash-reporter-id']; }
get crashReporterDirectory(): string | undefined { return this._args['crash-reporter-directory']; }
get driverHandle(): string | undefined { return this._args['driver']; }
@@ -262,6 +264,8 @@ export class EnvironmentService implements INativeEnvironmentService {
get disableTelemetry(): boolean { return !!this._args['disable-telemetry']; }
get sandbox(): boolean { return !!this._args['__sandbox']; }
constructor(private _args: ParsedArgs, private _execPath: string) {
if (!process.env['VSCODE_LOGS']) {
const key = toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '');

View File

@@ -14,14 +14,15 @@ import { Event } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { rimraf } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export class ExtensionsLifecycle extends Disposable {
private processesLimiter: Limiter<void> = new Limiter(5); // Run max 5 processes in parallel
constructor(
private environmentService: INativeEnvironmentService,
private logService: ILogService
@IEnvironmentService private environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService
) {
super();
}

View File

@@ -92,7 +92,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
@IInstantiationService instantiationService: IInstantiationService,
) {
super();
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner));
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
@@ -102,9 +103,6 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.installingExtensions.clear();
this.uninstallingExtensions.clear();
}));
const extensionLifecycle = this._register(new ExtensionsLifecycle(environmentService, this.logService));
this._register(this.extensionsScanner.onDidRemoveExtension(extension => extensionLifecycle.postUninstall(extension)));
}
zip(extension: ILocalExtension): Promise<URI> {

View File

@@ -23,7 +23,6 @@ import { CancellationToken } from 'vscode';
import { extract, ExtractError } from 'vs/base/node/zip';
import { isWindows } from 'vs/base/common/platform';
import { flatten } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
@@ -41,10 +40,8 @@ export class ExtensionsScanner extends Disposable {
private readonly uninstalledPath: string;
private readonly uninstalledFileLimiter: Queue<any>;
private _onDidRemoveExtension = new Emitter<ILocalExtension>();
readonly onDidRemoveExtension = this._onDidRemoveExtension.event;
constructor(
private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IProductService private readonly productService: IProductService,
@@ -280,7 +277,7 @@ export class ExtensionsScanner extends Disposable {
await Promise.all(byExtension.map(async e => {
const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
if (!installed.has(latest.identifier.id.toLowerCase())) {
this._onDidRemoveExtension.fire(latest);
await this.beforeRemovingExtension(latest);
}
}));
const toRemove: ILocalExtension[] = extensions.filter(e => uninstalled[new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version).key()]);

View File

@@ -85,6 +85,8 @@ export interface ProcessExplorerStyles extends WindowStyles {
export interface ProcessExplorerData extends WindowData {
pid: number;
styles: ProcessExplorerStyles;
platform: string;
applicationName: string;
}
export interface ICommonIssueService {

View File

@@ -199,6 +199,7 @@ export class IssueMainService implements ICommonIssueService {
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel)
}
@@ -250,11 +251,23 @@ export class IssueMainService implements ICommonIssueService {
title: localize('processExplorer', "Process Explorer"),
webPreferences: {
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
nodeIntegration: true,
enableWebSQL: false,
enableRemoteModule: false,
spellcheck: false,
nativeWindowOpen: true,
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel)
zoomFactor: zoomLevelToZoomFactor(data.zoomLevel),
...this.environmentService.sandbox ?
// Sandbox
{
sandbox: true,
contextIsolation: true
} :
// No Sandbox
{
nodeIntegration: true
}
}
});
@@ -270,7 +283,7 @@ export class IssueMainService implements ICommonIssueService {
};
this._processExplorerWindow.loadURL(
toLauchUrl('vs/code/electron-browser/processExplorer/processExplorer.html', windowConfiguration));
toLauchUrl('vs/code/electron-sandbox/processExplorer/processExplorer.html', windowConfiguration));
this._processExplorerWindow.on('close', () => this._processExplorerWindow = null);

View File

@@ -70,14 +70,18 @@ export class LaunchMainService implements ILaunchMainService {
@IConfigurationService private readonly configurationService: IConfigurationService
) { }
start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
async start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
this.logService.trace('Received data from other instance: ', args, userEnv);
const urlsToOpen = parseOpenUrl(args);
// Since we now start to open a window, make sure the app has focus.
// Focussing a window will not ensure that the application itself
// has focus, so we use the `steal: true` hint to force focus.
app.focus({ steal: true });
// Check early for open-url which is handled in URL service
const urlsToOpen = parseOpenUrl(args);
if (urlsToOpen.length) {
let whenWindowReady: Promise<any> = Promise.resolve<any>(null);
let whenWindowReady: Promise<unknown> = Promise.resolve();
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
@@ -91,12 +95,12 @@ export class LaunchMainService implements ILaunchMainService {
this.urlService.open(url);
}
});
return Promise.resolve(undefined);
}
// Otherwise handle in windows service
return this.startOpenWindow(args, userEnv);
else {
return this.startOpenWindow(args, userEnv);
}
}
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
@@ -156,8 +160,6 @@ export class LaunchMainService implements ILaunchMainService {
else {
const lastActive = this.windowsMainService.getLastActiveWindow();
if (lastActive) {
// Force focus the app before requesting window focus
app.focus({ steal: true });
lastActive.focus();
usedWindows = [lastActive];

View File

@@ -10,7 +10,7 @@ import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/b
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey, crashReporterIdStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
type Key = string;
type Value = string;
@@ -49,16 +49,6 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC
this.logService.error(`[storage] init(): Unable to init global storage due to ${error}`);
}
// This is unique to the application instance and thereby
// should be written from the main process once.
//
// THIS SHOULD NEVER BE SENT TO TELEMETRY.
//
const crashReporterId = this.storageMainService.get(crashReporterIdStorageKey, undefined);
if (crashReporterId === undefined) {
this.storageMainService.store(crashReporterIdStorageKey, generateUuid());
}
// Apply global telemetry values as part of the initialization
// These are global across all windows and thereby should be
// written from the main process once.

View File

@@ -55,4 +55,3 @@ export const currentSessionDateStorageKey = 'telemetry.currentSessionDate';
export const firstSessionDateStorageKey = 'telemetry.firstSessionDate';
export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
export const machineIdKey = 'telemetry.machineId';
export const crashReporterIdStorageKey = 'crashReporter.guid';

View File

@@ -10,7 +10,7 @@ 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
IUserDataManifest, ISyncData, IRemoteUserData, PREVIEW_DIR_NAME, IResourcePreview as IBaseResourcePreview, Change, MergeState
} from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname, isEqual, basename } from 'vs/base/common/resources';
@@ -57,11 +57,11 @@ export interface IMergableResourcePreview extends IBaseResourcePreview {
readonly remoteContent: string | null;
readonly localContent: string | null;
readonly previewContent: string | null;
readonly acceptedContent: string | null;
readonly hasConflicts: boolean;
merged: boolean;
}
export type IResourcePreview = Omit<IMergableResourcePreview, 'merged'>;
export type IResourcePreview = Omit<IMergableResourcePreview, 'mergeState'>;
export interface ISyncResourcePreview extends IBaseSyncResourcePreview {
readonly remoteUserData: IRemoteUserData;
@@ -217,6 +217,19 @@ export abstract class AbstractSynchroniser extends Disposable {
return this._sync(manifest, false, headers);
}
async apply(force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
const status = await this.doApply(force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
}
private async _sync(manifest: IUserDataManifest | null, apply: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null> {
try {
this.syncHeaders = { ...headers };
@@ -350,14 +363,18 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncPreviewPromise = createCancelablePromise(token => this.doGenerateSyncResourcePreview(remoteUserData, lastSyncUserData, apply, token));
}
if (apply) {
const preview = await this.syncPreviewPromise;
const newConflicts = preview.resourcePreviews.filter(({ hasConflicts }) => hasConflicts);
return await this.updateConflictsAndApply(newConflicts, false);
} else {
return SyncStatus.Syncing;
const preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
if (apply) {
return await this.doApply(false);
}
return SyncStatus.Syncing;
} catch (error) {
// reset preview on error
@@ -367,76 +384,91 @@ export abstract class AbstractSynchroniser extends Disposable {
}
}
async acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
async accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resource, content);
return {
...updatedResourcePreview,
mergeState: MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async merge(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted
};
});
return this.syncPreviewPromise;
}
async discard(resource: URI): Promise<ISyncResourcePreview | null> {
await this.updateSyncResourcePreview(resource, async (resourcePreview) => {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
const updatedResourcePreview = await this.updateResourcePreview(resourcePreview, resourcePreview.previewResource, resourcePreview.previewContent);
return {
...updatedResourcePreview,
mergeState: MergeState.Preview
};
});
return this.syncPreviewPromise;
}
private async updateSyncResourcePreview(resource: URI, updateResourcePreview: (resourcePreview: IMergableResourcePreview) => Promise<IMergableResourcePreview>): Promise<void> {
if (!this.syncPreviewPromise) {
return null;
return;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
this.syncPreviewPromise = createCancelablePromise(token => this.updateSyncResourcePreviewContent(preview, resource, content, token));
return this.merge(resource, force, headers);
} finally {
this.syncHeaders = {};
let preview = await this.syncPreviewPromise;
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (index === -1) {
return;
}
this.syncPreviewPromise = createCancelablePromise(async token => {
const resourcePreviews = [...preview.resourcePreviews];
resourcePreviews[index] = await updateResourcePreview(resourcePreviews[index]);
return {
...preview,
resourcePreviews
};
});
preview = await this.syncPreviewPromise;
this.updateConflicts(preview.resourcePreviews);
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
this.setStatus(SyncStatus.HasConflicts);
} else {
this.setStatus(SyncStatus.Syncing);
}
}
async merge(resource: URI, force: boolean, headers: IHeaders = {}): Promise<ISyncResourcePreview | null> {
if (!this.syncPreviewPromise) {
return null;
}
try {
this.syncHeaders = { ...headers };
const preview = await this.syncPreviewPromise;
const resourcePreview = preview.resourcePreviews.find(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (!resourcePreview) {
return preview;
}
/* mark merged */
resourcePreview.merged = true;
/* Add or remove the preview from conflicts */
const newConflicts = [...this._conflicts];
const index = newConflicts.findIndex(({ localResource, remoteResource, previewResource }) =>
isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource));
if (resourcePreview.hasConflicts) {
if (newConflicts.indexOf(resourcePreview) === -1) {
newConflicts.push(resourcePreview);
}
} else {
if (index !== -1) {
newConflicts.splice(index, 1);
}
}
const status = await this.updateConflictsAndApply(newConflicts, force);
this.setStatus(status);
return this.syncPreviewPromise;
} finally {
this.syncHeaders = {};
}
protected async updateResourcePreview(resourcePreview: IResourcePreview, resource: URI, acceptedContent: string | null): Promise<IResourcePreview> {
return {
...resourcePreview,
acceptedContent
};
}
private async updateConflictsAndApply(conflicts: IMergableResourcePreview[], force: boolean): Promise<SyncStatus> {
private async doApply(force: boolean): Promise<SyncStatus> {
if (!this.syncPreviewPromise) {
return SyncStatus.Idle;
}
const preview = await this.syncPreviewPromise;
// update conflicts
this.updateConflicts(conflicts);
if (this._conflicts.length) {
// check for conflicts
if (preview.resourcePreviews.some(({ mergeState }) => mergeState === MergeState.Conflict)) {
return SyncStatus.HasConflicts;
}
// check if all are merged
if (preview.resourcePreviews.some(r => !r.merged)) {
// check if all are accepted
if (preview.resourcePreviews.some(({ mergeState }) => mergeState !== MergeState.Accepted)) {
return SyncStatus.Syncing;
}
@@ -452,39 +484,14 @@ export abstract class AbstractSynchroniser extends Disposable {
return SyncStatus.Idle;
}
private async updateSyncResourcePreviewContent(preview: ISyncResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<ISyncResourcePreview> {
const index = preview.resourcePreviews.findIndex(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
(localChange !== Change.None || remoteChange !== Change.None)
&& (isEqual(localResource, resource) || isEqual(remoteResource, resource) || isEqual(previewResource, resource)));
if (index !== -1) {
const resourcePreviews = [...preview.resourcePreviews];
const resourcePreview = await this.updateResourcePreviewContent(resourcePreviews[index], resource, previewContent, token);
resourcePreviews[index] = { ...resourcePreview, merged: resourcePreviews[index].merged };
preview = {
...preview,
resourcePreviews
};
}
return preview;
}
protected async updateResourcePreviewContent(resourcePreview: IResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IResourcePreview> {
return {
...resourcePreview,
previewContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
};
}
private async clearPreviewFolder(): Promise<void> {
try {
await this.fileService.del(this.syncPreviewFolder, { recursive: true });
} catch (error) { /* Ignore */ }
}
private updateConflicts(conflicts: IMergableResourcePreview[]): void {
private updateConflicts(previews: IMergableResourcePreview[]): void {
const conflicts = previews.filter(p => p.mergeState === MergeState.Conflict);
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.previewResource, b.previewResource))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(conflicts);
@@ -542,14 +549,14 @@ export abstract class AbstractSynchroniser extends Disposable {
const syncPreview = this.syncPreviewPromise ? await this.syncPreviewPromise : null;
if (syncPreview) {
for (const resourcePreview of syncPreview.resourcePreviews) {
if (resourcePreview.previewResource && isEqual(resourcePreview.previewResource, uri)) {
return resourcePreview.previewContent || '';
if (isEqual(resourcePreview.acceptedResource, uri)) {
return resourcePreview.acceptedContent;
}
if (resourcePreview.remoteResource && isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent || '';
if (isEqual(resourcePreview.remoteResource, uri)) {
return resourcePreview.remoteContent;
}
if (resourcePreview.localResource && isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent || '';
if (isEqual(resourcePreview.localResource, uri)) {
return resourcePreview.localContent;
}
}
}
@@ -562,21 +569,31 @@ export abstract class AbstractSynchroniser extends Disposable {
} catch (e) { /* ignore */ }
}
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, merge: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
private async doGenerateSyncResourcePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, apply: boolean, token: CancellationToken): Promise<ISyncResourcePreview> {
const machineId = await this.currentMachineIdPromise;
const isLastSyncFromCurrentMachine = !!remoteUserData.syncData?.machineId && remoteUserData.syncData.machineId === machineId;
// For preview, use remoteUserData if lastSyncUserData does not exists and last sync is from current machine
const lastSyncUserDataForPreview = lastSyncUserData === null && isLastSyncFromCurrentMachine ? remoteUserData : lastSyncUserData;
const resourcePreviews = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
const result = await this.generateSyncPreview(remoteUserData, lastSyncUserDataForPreview, token);
// Mark merge
const mergableResourcePreviews = resourcePreviews.map(r => ({
...r,
merged: merge || (r.localChange === Change.None && r.remoteChange === Change.None) /* Mark previews with no changes as merged */
}));
const resourcePreviews: IMergableResourcePreview[] = [];
for (const resourcePreview of result) {
if (token.isCancellationRequested) {
break;
}
if (!apply) {
await this.fileService.writeFile(resourcePreview.previewResource, VSBuffer.fromString(resourcePreview.previewContent || ''));
}
resourcePreviews.push({
...resourcePreview,
mergeState: resourcePreview.localChange === Change.None && resourcePreview.remoteChange === Change.None ? MergeState.Accepted /* Mark previews with no changes as merged */
: apply ? (resourcePreview.hasConflicts ? MergeState.Conflict : MergeState.Accepted)
: MergeState.Preview
});
}
return { remoteUserData, lastSyncUserData, resourcePreviews: mergableResourcePreviews, isLastSyncFromCurrentMachine };
return { remoteUserData, lastSyncUserData, resourcePreviews, isLastSyncFromCurrentMachine };
}
async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {

View File

@@ -46,8 +46,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
*/
protected readonly version: number = 3;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
private readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'extensions.json');
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = 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' });
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@@ -94,12 +96,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const mergeResult = merge(localExtensions, syncExtensions, localExtensions, [], ignoredExtensions);
const { added, removed, updated } = mergeResult;
return [{
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -131,12 +135,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = mergeResult;
return [{
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -177,14 +183,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
}
protected async updateResourcePreviewContent(resourcePreview: IExtensionResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IExtensionResourcePreview> {
if (isEqual(resource, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
protected async updateResourcePreview(resourcePreview: IExtensionResourcePreview, resource: URI, acceptedContent: string | null): Promise<IExtensionResourcePreview> {
if (isEqual(resource, this.localResource)) {
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
return this.getPushPreview(remoteExtensions);
}
return {
...resourcePreview,
previewContent,
acceptedContent,
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.Modified,
@@ -195,10 +201,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
const ignoredExtensions = getIgnoredExtensions(installedExtensions, this.configurationService);
const localResource = ExtensionsSynchroniser.EXTENSIONS_DATA_URI;
const localResource = this.localResource;
const localContent = this.format(localExtensions);
const remoteResource = this.remotePreviewResource;
const previewResource = this.localPreviewResource;
const remoteResource = this.remoteResource;
const previewResource = this.previewResource;
const acceptedResource = this.acceptedResource;
const previewContent = null;
if (remoteExtensions !== null) {
const mergeResult = merge(localExtensions, remoteExtensions, localExtensions, [], ignoredExtensions);
@@ -210,6 +217,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
remoteContent: this.format(remoteExtensions),
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
added,
removed,
updated,
@@ -228,6 +237,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
remoteContent: null,
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
added: [], removed: [], updated: [], remote: null, localExtensions, skippedExtensions: [],
localChange: Change.None,
remoteChange: Change.None,
@@ -243,12 +254,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const mergeResult = merge(localExtensions, null, null, [], ignoredExtensions);
const { added, removed, updated, remote } = mergeResult;
return {
localResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI,
localResource: this.localResource,
localContent: this.format(localExtensions),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteExtensions ? this.format(remoteExtensions) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
added,
removed,
updated,
@@ -266,13 +279,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
return this.format(localExtensions);
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}

View File

@@ -45,8 +45,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
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 localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'globalState.json');
private readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = 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' });
constructor(
@IFileService fileService: IFileService,
@@ -93,12 +95,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const mergeResult = merge(localUserData.storage, syncGlobalState.storage, localUserData.storage, this.getSyncStorageKeys(), lastSyncUserData?.skippedStorageKeys || [], this.logService);
const { local, skipped } = mergeResult;
return [{
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localUserData),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local,
remote: syncGlobalState.storage,
localUserData,
@@ -125,12 +129,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const { local, remote, skipped } = mergeResult;
return [{
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localGloablState),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local,
remote,
localUserData: localGloablState,
@@ -172,11 +178,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
}
protected async updateResourcePreviewContent(resourcePreview: IGlobalStateResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IGlobalStateResourcePreview> {
if (GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI, resource) {
protected async updateResourcePreview(resourcePreview: IGlobalStateResourcePreview, resource: URI, acceptedContent: string | null): Promise<IGlobalStateResourcePreview> {
if (isEqual(this.localResource, resource)) {
return this.getPushPreview(resourcePreview.remoteContent);
}
if (this.remotePreviewResource, resource) {
if (isEqual(this.remoteResource, resource)) {
return this.getPullPreview(resourcePreview.remoteContent, resourcePreview.skippedStorageKeys);
}
return resourcePreview;
@@ -184,10 +190,11 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
private async getPullPreview(remoteContent: string | null, skippedStorageKeys: string[]): Promise<IGlobalStateResourcePreview> {
const localGlobalState = await this.getLocalGlobalState();
const localResource = GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI;
const localResource = this.localResource;
const localContent = this.format(localGlobalState);
const remoteResource = this.remotePreviewResource;
const previewResource = this.localPreviewResource;
const remoteResource = this.remoteResource;
const previewResource = this.previewResource;
const acceptedResource = this.acceptedResource;
const previewContent = null;
if (remoteContent !== null) {
const remoteGlobalState: IGlobalState = JSON.parse(remoteContent);
@@ -200,6 +207,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
remoteContent: this.format(remoteGlobalState),
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
local,
remote,
localUserData: localGlobalState,
@@ -216,6 +225,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
remoteContent: null,
previewResource,
previewContent,
acceptedResource,
acceptedContent: previewContent,
local: { added: {}, removed: [], updated: {} },
remote: null,
localUserData: localGlobalState,
@@ -231,12 +242,14 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const localUserData = await this.getLocalGlobalState();
const remoteGlobalState: IGlobalState = remoteContent ? JSON.parse(remoteContent) : null;
return {
localResource: GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI,
localResource: this.localResource,
localContent: this.format(localUserData),
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteGlobalState ? this.format(remoteGlobalState) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent: null,
acceptedResource: this.acceptedResource,
acceptedContent: null,
local: { added: {}, removed: [], updated: {} },
remote: localUserData.storage,
localUserData,
@@ -252,12 +265,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(uri, GlobalStateSynchroniser.GLOBAL_STATE_DATA_URI)) {
const localGlobalState = await this.getLocalGlobalState();
return this.format(localGlobalState);
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}

View File

@@ -35,8 +35,10 @@ interface ISyncContent {
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
protected readonly version: number = 1;
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'keybindings.json');
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = 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' });
constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -58,13 +60,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: previewContent,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: Change.None,
hasConflicts: false,
@@ -76,13 +80,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent: string | null = fileContent ? fileContent.value.toString() : null;
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -94,13 +100,15 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const previewContent = this.getKeybindingsContentFromSyncContent(syncData.content);
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -150,17 +158,19 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
if (previewContent && !token.isCancellationRequested) {
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(previewContent));
await this.fileService.writeFile(this.previewResource, VSBuffer.fromString(previewContent));
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
hasConflicts,
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
@@ -168,7 +178,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
if (content !== null) {
if (this.hasErrors(content)) {
@@ -193,7 +203,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
// Delete the preview
try {
await this.fileService.del(this.localPreviewResource);
await this.fileService.del(this.previewResource);
} catch (e) { /* ignore */ }
} else {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
@@ -230,11 +240,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.file, uri)) {
const fileContent = await this.getLocalFileContent();
return fileContent ? fileContent.value.toString() : '';
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);

View File

@@ -39,8 +39,10 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
protected readonly version: number = 1;
protected readonly localPreviewResource: URI = joinPath(this.syncPreviewFolder, 'settings.json');
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
private readonly previewResource: URI = joinPath(this.syncPreviewFolder, 'settings.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' });
constructor(
@IFileService fileService: IFileService,
@@ -66,19 +68,21 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
let previewContent: string | null = null;
if (remoteSettingsSyncContent !== null) {
if (remoteSettingsSyncContent) {
// Update ignored settings from local file content
previewContent = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: Change.None,
hasConflicts: false,
@@ -92,20 +96,22 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const ignoredSettings = await this.getIgnoredSettings();
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
let previewContent: string | null = null;
if (fileContent !== null) {
let previewContent: string | null = fileContent?.value.toString() || null;
if (previewContent) {
// Remove ignored settings
previewContent = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils);
previewContent = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formatUtils);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -126,13 +132,15 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent: previewContent,
localChange: previewContent !== null ? Change.Modified : Change.None,
remoteChange: previewContent !== null ? Change.Modified : Change.None,
hasConflicts: false,
@@ -146,6 +154,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const lastSettingsSyncContent: ISettingsSyncContent | null = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
const ignoredSettings = await this.getIgnoredSettings();
let acceptedContent: string | null = null;
let previewContent: string | null = null;
let hasLocalChanged: boolean = false;
let hasRemoteChanged: boolean = false;
@@ -156,7 +165,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
this.validateContent(localContent);
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote settings with local settings...`);
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, [], formattingOptions);
previewContent = result.localContent || result.remoteContent;
acceptedContent = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
@@ -165,40 +174,43 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// First time syncing to remote
else if (fileContent) {
this.logService.trace(`${this.syncResourceLogLabel}: Remote settings does not exist. Synchronizing settings for the first time.`);
previewContent = fileContent.value.toString();
acceptedContent = fileContent.value.toString();
hasRemoteChanged = true;
}
if (previewContent && !token.isCancellationRequested) {
if (acceptedContent && !token.isCancellationRequested) {
// Remove the ignored settings from the preview.
const content = updateIgnoredSettings(previewContent, '{}', ignoredSettings, formattingOptions);
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
previewContent = updateIgnoredSettings(acceptedContent, '{}', ignoredSettings, formattingOptions);
}
return [{
localResource: this.file,
localResource: this.localResource,
fileContent,
localContent: fileContent ? fileContent.value.toString() : null,
remoteResource: this.remotePreviewResource,
remoteResource: this.remoteResource,
remoteContent: remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : null,
previewResource: this.localPreviewResource,
previewResource: this.previewResource,
previewContent,
acceptedResource: this.acceptedResource,
acceptedContent,
localChange: hasLocalChanged ? fileContent ? Change.Modified : Change.Added : Change.None,
remoteChange: hasRemoteChanged ? Change.Modified : Change.None,
hasConflicts,
}];
}
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
const formatUtils = await this.getFormattingOptions();
// Add ignored settings from local file content
const ignoredSettings = await this.getIgnoredSettings();
previewContent = updateIgnoredSettings(previewContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
return super.updateResourcePreviewContent(resourcePreview, resource, previewContent, token) as Promise<IFileResourcePreview>;
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
if (acceptedContent && (isEqual(resource, this.previewResource) || isEqual(resource, this.remoteResource))) {
const formatUtils = await this.getFormattingOptions();
// Add ignored settings from local file content
const ignoredSettings = await this.getIgnoredSettings();
acceptedContent = updateIgnoredSettings(acceptedContent, resourcePreview.fileContent ? resourcePreview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils);
}
return super.updateResourcePreview(resourcePreview, resource, acceptedContent) as Promise<IFileResourcePreview>;
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
let { fileContent, previewContent: content, localChange, remoteChange } = resourcePreviews[0];
let { fileContent, acceptedContent: content, localChange, remoteChange } = resourcePreviews[0];
if (content !== null) {
@@ -225,7 +237,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
// Delete the preview
try {
await this.fileService.del(this.localPreviewResource);
await this.fileService.del(this.previewResource);
} catch (e) { /* ignore */ }
} else {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing settings.`);
@@ -260,11 +272,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqual(this.file, uri)) {
const fileContent = await this.getLocalFileContent();
return fileContent ? fileContent.value.toString() : '';
}
if (isEqual(this.remotePreviewResource, uri) || isEqual(this.localPreviewResource, uri)) {
if (isEqual(this.remoteResource, uri) || isEqual(this.localResource, uri) || isEqual(this.acceptedResource, uri)) {
return this.resolvePreviewContent(uri);
}
let content = await super.resolveContent(uri);
@@ -289,7 +297,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
protected async resolvePreviewContent(resource: URI): Promise<string | null> {
let content = await super.resolvePreviewContent(resource);
if (content !== null) {
if (content) {
const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the preview content
const ignoredSettings = await this.getIgnoredSettings();

View File

@@ -5,7 +5,7 @@
import {
IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncResourceEnablementService, IUserDataSyncBackupStoreService,
USER_DATA_SYNC_SCHEME, ISyncResourceHandle, IRemoteUserData, ISyncData, UserDataSyncError, UserDataSyncErrorCode, Change
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';
@@ -20,7 +20,6 @@ import { merge, IMergeResult, areSame } from 'vs/platform/userDataSync/common/sn
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { deepClone } from 'vs/base/common/objects';
import { localize } from 'vs/nls';
export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
@@ -94,34 +93,93 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
const mergeResult = merge(localSnippets, remoteSnippets, lastSyncSnippets);
const resourcePreviews = this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
for (const resourcePreview of resourcePreviews) {
if (resourcePreview.hasConflicts) {
if (!token.isCancellationRequested) {
await this.fileService.writeFile(resourcePreview.previewResource!, VSBuffer.fromString(resourcePreview.previewContent || ''));
}
}
}
return resourcePreviews;
return this.getResourcePreviews(mergeResult, local, remoteSnippets || {});
}
protected async updateResourcePreviewContent(resourcePreview: IFileResourcePreview, resource: URI, previewContent: string, token: CancellationToken): Promise<IFileResourcePreview> {
protected async updateResourcePreview(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Promise<IFileResourcePreview> {
return {
...resourcePreview,
previewContent: previewContent || null,
hasConflicts: false,
localChange: previewContent ? Change.Modified : Change.Deleted,
remoteChange: previewContent ? Change.Modified : Change.Deleted,
acceptedContent,
localChange: this.computeLocalChange(resourcePreview, resource, acceptedContent),
remoteChange: this.computeRemoteChange(resourcePreview, resource, acceptedContent),
};
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
throw new UserDataSyncError(localize('unresolved conflicts', "Error while syncing {0}. Please resolve conflicts first.", this.syncResourceLogLabel), UserDataSyncErrorCode.UnresolvedConflicts, this.resource);
private computeLocalChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
const isRemoteResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }));
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
const previewExists = acceptedContent !== null;
const remoteExists = resourcePreview.remoteContent !== null;
const localExists = resourcePreview.fileContent !== null;
if (isRemoteResourceAccepted) {
if (remoteExists && localExists) {
return Change.Modified;
}
if (remoteExists && !localExists) {
return Change.Added;
}
if (!remoteExists && localExists) {
return Change.Deleted;
}
return Change.None;
}
if (isPreviewResourceAccepted) {
if (previewExists && localExists) {
return Change.Modified;
}
if (previewExists && !localExists) {
return Change.Added;
}
if (!previewExists && localExists) {
return Change.Deleted;
}
return Change.None;
}
return Change.None;
}
private computeRemoteChange(resourcePreview: IFileResourcePreview, resource: URI, acceptedContent: string | null): Change {
const isLocalResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'local' }));
const isPreviewResourceAccepted = isEqualOrParent(resource, this.syncPreviewFolder);
const previewExists = acceptedContent !== null;
const remoteExists = resourcePreview.remoteContent !== null;
const localExists = resourcePreview.fileContent !== null;
if (isLocalResourceAccepted) {
if (remoteExists && localExists) {
return Change.Modified;
}
if (remoteExists && !localExists) {
return Change.Deleted;
}
if (!remoteExists && localExists) {
return Change.Added;
}
return Change.None;
}
if (isPreviewResourceAccepted) {
if (previewExists && remoteExists) {
return Change.Modified;
}
if (previewExists && !remoteExists) {
return Change.Added;
}
if (!previewExists && remoteExists) {
return Change.Deleted;
}
return Change.None;
}
return Change.None;
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.every(({ localChange, remoteChange }) => localChange === Change.None && remoteChange === Change.None)) {
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing snippets.`);
}
@@ -158,13 +216,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets added remotely -> add locally */
for (const key of Object.keys(mergeResult.local.added)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.local.added[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.local.added[key],
hasConflicts: false,
localChange: Change.Added,
remoteChange: Change.None
@@ -174,13 +234,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets updated remotely -> update locally */
for (const key of Object.keys(mergeResult.local.updated)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.local.updated[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.local.updated[key],
hasConflicts: false,
localChange: Change.Modified,
remoteChange: Change.None
@@ -190,13 +252,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets removed remotely -> remove locally */
for (const key of mergeResult.local.removed) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: null,
hasConflicts: false,
localChange: Change.Deleted,
remoteChange: Change.None
@@ -206,13 +270,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets added locally -> add remotely */
for (const key of Object.keys(mergeResult.remote.added)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.remote.added[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.remote.added[key],
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Added
@@ -222,13 +288,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets updated locally -> update remotely */
for (const key of Object.keys(mergeResult.remote.updated)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: mergeResult.remote.updated[key],
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: mergeResult.remote.updated[key],
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Modified
@@ -238,13 +306,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets removed locally -> remove remotely */
for (const key of mergeResult.remote.removed) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key],
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: null,
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.Deleted
@@ -254,13 +324,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
/* Snippets with conflicts */
for (const key of mergeResult.conflicts) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
hasConflicts: true,
localChange: localFileContent[key] ? Change.Modified : Change.Added,
remoteChange: remoteSnippets[key] ? Change.Modified : Change.Added
@@ -271,13 +343,15 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
for (const key of Object.keys(localFileContent)) {
if (!resourcePreviews.has(key)) {
resourcePreviews.set(key, {
localResource: joinPath(this.snippetsFolder, key),
localResource: 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 }),
remoteResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'remote' }),
remoteContent: remoteSnippets[key] || null,
previewResource: joinPath(this.syncPreviewFolder, key),
previewContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
acceptedResource: joinPath(this.syncPreviewFolder, key).with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'accepted' }),
acceptedContent: localFileContent[key] ? localFileContent[key].value.toString() : null,
hasConflicts: false,
localChange: Change.None,
remoteChange: Change.None
@@ -308,17 +382,9 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
async resolveContent(uri: URI): Promise<string | null> {
if (isEqualOrParent(uri, this.snippetsFolder)) {
try {
const content = await this.fileService.readFile(uri);
return content ? content.value.toString() : null;
} catch (error) {
return '';
}
}
if (isEqualOrParent(uri.with({ scheme: this.syncPreviewFolder.scheme }), this.syncPreviewFolder)
|| isEqualOrParent(uri, this.syncPreviewFolder.with({ scheme: USER_DATA_SYNC_SCHEME }))) {
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' }))) {
return this.resolvePreviewContent(uri);
}
@@ -362,12 +428,7 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
private async updateLocalSnippets(resourcePreviews: IFileResourcePreview[], force: boolean): Promise<void> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
// Do not update if there are conflicts
return;
}
for (const { fileContent, previewContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
for (const { fileContent, acceptedContent: content, localResource, remoteResource, localChange } of resourcePreviews) {
if (localChange !== Change.None) {
const key = remoteResource ? basename(remoteResource) : basename(localResource!);
const resource = joinPath(this.snippetsFolder, key);
@@ -397,15 +458,10 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
}
private async updateRemoteSnippets(resourcePreviews: IFileResourcePreview[], remoteUserData: IRemoteUserData, forcePush: boolean): Promise<IRemoteUserData> {
if (resourcePreviews.some(({ hasConflicts }) => hasConflicts)) {
// Do not update if there are conflicts
return remoteUserData;
}
const currentSnippets: IStringDictionary<string> = remoteUserData.syncData ? this.parseSnippets(remoteUserData.syncData) : {};
const newSnippets: IStringDictionary<string> = deepClone(currentSnippets);
for (const { previewContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
for (const { acceptedContent: content, localResource, remoteResource, remoteChange } of resourcePreviews) {
if (remoteChange !== Change.None) {
const key = localResource ? basename(localResource) : basename(remoteResource!);
if (remoteChange === Change.Deleted) {

View File

@@ -15,6 +15,7 @@ import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { localize } from 'vs/nls';
import { toLocalISOString } from 'vs/base/common/date';
type AutoSyncClassification = {
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -96,18 +97,24 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
this.syncTriggerDelayer = this._register(new Delayer<void>(0));
if (userDataSyncStoreService.userDataSyncStore) {
if (this.isEnabled()) {
this.logService.info('Auto Sync is enabled.');
} else {
this.logService.info('Auto Sync is disabled.');
}
this.updateAutoSync();
if (this.hasToDisableMachineEventually()) {
this.disableMachineEventually();
}
this._register(userDataSyncAccountService.onDidChangeAccount(() => this.updateAutoSync()));
this._register(userDataSyncStoreService.onDidChangeDonotMakeRequestsUntil(() => this.updateAutoSync()));
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));
this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false)));
}
}
private updateAutoSync(): void {
const { enabled, reason } = this.isAutoSyncEnabled();
const { enabled, message } = this.isAutoSyncEnabled();
if (enabled) {
if (this.autoSync.value === undefined) {
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService);
@@ -120,21 +127,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
} else {
this.syncTriggerDelayer.cancel();
if (this.autoSync.value !== undefined) {
this.logService.info('Auto Sync: Disabled because', reason);
if (message) {
this.logService.info(message);
}
this.autoSync.clear();
}
/* log message when auto sync is not disabled by user */
else if (message && this.isEnabled()) {
this.logService.info(message);
}
}
}
// For tests purpose only
protected startAutoSync(): boolean { return true; }
private isAutoSyncEnabled(): { enabled: boolean, reason?: string } {
private isAutoSyncEnabled(): { enabled: boolean, message?: string } {
if (!this.isEnabled()) {
return { enabled: false, reason: 'sync is disabled' };
return { enabled: false, message: 'Auto Sync: Disabled.' };
}
if (!this.userDataSyncAccountService.account) {
return { enabled: false, reason: 'token is not avaialable' };
return { enabled: false, message: 'Auto Sync: Suspended until auth token is available.' };
}
if (this.userDataSyncStoreService.donotMakeRequestsUntil) {
return { enabled: false, message: `Auto Sync: Suspended until ${toLocalISOString(this.userDataSyncStoreService.donotMakeRequestsUntil)} because server is not accepting requests until then.` };
}
return { enabled: true };
}

View File

@@ -122,16 +122,19 @@ export function isAuthenticationProvider(thing: any): thing is IAuthenticationPr
}
export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined {
const value = configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY];
const value = {
...(productService[CONFIGURATION_SYNC_STORE_KEY] || {}),
...(configurationService.getValue<ConfigurationSyncStore>(CONFIGURATION_SYNC_STORE_KEY) || {})
};
if (value
&& isString(value.url)
&& isObject(value.authenticationProviders)
&& Object.keys(value.authenticationProviders).every(authenticationProviderId => isArray(value.authenticationProviders[authenticationProviderId].scopes))
&& isString((value as any).url) // {{SQL CARBON EDIT}} strict-nulls
&& isObject((value as any).authenticationProviders) // {{SQL CARBON EDIT}} strict-nulls
&& Object.keys((value as any).authenticationProviders).every(authenticationProviderId => isArray((value as any).authenticationProviders[authenticationProviderId].scopes)) // {{SQL CARBON EDIT}} strict-nulls
) {
return {
url: joinPath(URI.parse(value.url), 'v1'),
authenticationProviders: Object.keys(value.authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
result.push({ id, scopes: value.authenticationProviders[id].scopes });
url: joinPath(URI.parse((value as any).url), 'v1'), // {{SQL CARBON EDIT}} strict-nulls
authenticationProviders: Object.keys((value as any).authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => { // {{SQL CARBON EDIT}} strict-nulls
result.push({ id, scopes: (value as any).authenticationProviders[id].scopes }); // {{SQL CARBON EDIT}} strict-nulls
return result;
}, [])
};
@@ -164,6 +167,9 @@ export interface IUserDataSyncStoreService {
readonly _serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
readonly donotMakeRequestsUntil: Date | undefined;
readonly onTokenFailed: Event<void>;
readonly onTokenSucceed: Event<void>;
setAuthToken(token: string, type: string): void;
@@ -207,6 +213,7 @@ export enum UserDataSyncErrorCode {
UpgradeRequired = 'UpgradeRequired', /* 426 */
PreconditionRequired = 'PreconditionRequired', /* 428 */
TooManyRequests = 'RemoteTooManyRequests', /* 429 */
TooManyRequestsAndRetryAfter = 'TooManyRequestsAndRetryAfter', /* 429 + Retry-After */
// Local Errors
ConnectionRefused = 'ConnectionRefused',
@@ -317,13 +324,20 @@ export const enum Change {
Deleted,
}
export const enum MergeState {
Preview = 'preview',
Conflict = 'conflict',
Accepted = 'accepted',
}
export interface IResourcePreview {
readonly remoteResource: URI;
readonly localResource: URI;
readonly previewResource: URI;
readonly acceptedResource: URI;
readonly localChange: Change;
readonly remoteChange: Change;
readonly merged: boolean;
readonly mergeState: MergeState;
}
export interface ISyncResourcePreview {
@@ -345,18 +359,20 @@ export interface IUserDataSynchroniser {
pull(): Promise<void>;
push(): Promise<void>;
sync(manifest: IUserDataManifest | null, headers: IHeaders): Promise<void>;
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
replace(uri: URI): Promise<boolean>;
stop(): Promise<void>;
preview(manifest: IUserDataManifest | null, headers: IHeaders): Promise<ISyncResourcePreview | null>;
accept(resource: URI, content: string | null): Promise<ISyncResourcePreview | null>;
merge(resource: URI): Promise<ISyncResourcePreview | null>;
discard(resource: URI): Promise<ISyncResourcePreview | null>;
apply(force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
hasPreviouslySynced(): Promise<boolean>;
hasLocalData(): Promise<boolean>;
resetLocal(): Promise<void>;
resolveContent(resource: URI): Promise<string | null>;
acceptPreviewContent(resource: URI, content: string, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
merge(resource: URI, force: boolean, headers: IHeaders): Promise<ISyncResourcePreview | null>;
getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
getLocalSyncResourceHandles(): Promise<ISyncResourceHandle[]>;
getAssociatedResources(syncResourceHandle: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]>;
@@ -387,8 +403,10 @@ export interface IManualSyncTask extends IDisposable {
readonly manifest: IUserDataManifest | null;
readonly onSynchronizeResources: Event<[SyncResource, URI[]][]>;
preview(): Promise<[SyncResource, ISyncResourcePreview][]>;
accept(uri: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]>;
merge(uri?: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]>;
merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]>;
apply(): Promise<[SyncResource, ISyncResourcePreview][]>;
pull(): Promise<void>;
push(): Promise<void>;
stop(): Promise<void>;
@@ -422,7 +440,7 @@ export interface IUserDataSyncService {
hasLocalData(): Promise<boolean>;
hasPreviouslySynced(): Promise<boolean>;
resolveContent(resource: URI): Promise<string | null>;
acceptPreviewContent(resource: SyncResource, conflictResource: URI, content: string): Promise<void>;
accept(resource: SyncResource, conflictResource: URI, content: string | null, apply: boolean): Promise<void>;
getLocalSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;
getRemoteSyncResourceHandles(resource: SyncResource): Promise<ISyncResourceHandle[]>;

View File

@@ -53,7 +53,7 @@ export class UserDataSyncChannel implements IServerChannel {
case 'resetLocal': return this.service.resetLocal();
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
case 'hasLocalData': return this.service.hasLocalData();
case 'acceptPreviewContent': return this.service.acceptPreviewContent(args[0], URI.revive(args[1]), args[2]);
case 'accept': return this.service.accept(args[0], URI.revive(args[1]), args[2], args[3]);
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
case 'getLocalSyncResourceHandles': return this.service.getLocalSyncResourceHandles(args[0]);
case 'getRemoteSyncResourceHandles': return this.service.getRemoteSyncResourceHandles(args[0]);
@@ -65,7 +65,7 @@ export class UserDataSyncChannel implements IServerChannel {
private async createManualSyncTask(): Promise<{ id: string, manifest: IUserDataManifest | null }> {
const manualSyncTask = await this.service.createManualSyncTask();
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask);
const manualSyncTaskChannel = new ManualSyncTaskChannel(manualSyncTask, this.logService);
this.server.registerChannel(`manualSyncTask-${manualSyncTask.id}`, manualSyncTaskChannel);
return { id: manualSyncTask.id, manifest: manualSyncTask.manifest };
}
@@ -73,7 +73,10 @@ export class UserDataSyncChannel implements IServerChannel {
class ManualSyncTaskChannel implements IServerChannel {
constructor(private readonly manualSyncTask: IManualSyncTask) { }
constructor(
private readonly manualSyncTask: IManualSyncTask,
private readonly logService: ILogService
) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
@@ -83,10 +86,22 @@ class ManualSyncTaskChannel implements IServerChannel {
}
async call(context: any, command: string, args?: any): Promise<any> {
try {
const result = await this._call(context, command, args);
return result;
} catch (e) {
this.logService.error(e);
throw e;
}
}
private async _call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'preview': return this.manualSyncTask.preview();
case 'accept': return this.manualSyncTask.accept(URI.revive(args[0]), args[1]);
case 'merge': return this.manualSyncTask.merge(URI.revive(args[0]));
case 'discard': return this.manualSyncTask.discard(URI.revive(args[0]));
case 'apply': return this.manualSyncTask.apply();
case 'pull': return this.manualSyncTask.pull();
case 'push': return this.manualSyncTask.push();
case 'stop': return this.manualSyncTask.stop();

View File

@@ -5,7 +5,7 @@
import {
IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode,
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID
UserDataSyncError, ISyncResourceHandle, IUserDataManifest, ISyncTask, IResourcePreview, IManualSyncTask, ISyncResourcePreview, HEADER_EXECUTION_ID, MergeState, Change
} from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -102,11 +102,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await this.checkEnablement();
try {
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.pull();
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
}
await synchroniser.pull();
}
this.updateLastSyncTime();
} catch (error) {
@@ -121,11 +117,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
await this.checkEnablement();
try {
for (const synchroniser of this.synchronisers) {
try {
await synchroniser.push();
} catch (e) {
this.handleSynchronizerError(e, synchroniser.resource);
}
await synchroniser.push();
}
this.updateLastSyncTime();
} catch (error) {
@@ -264,10 +256,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
async acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string, executionId: string = generateUuid()): Promise<void> {
async accept(syncResource: SyncResource, resource: URI, content: string | null, apply: boolean): Promise<void> {
await this.checkEnablement();
const synchroniser = this.getSynchroniser(syncResource);
await synchroniser.acceptPreviewContent(resource, content, false, createSyncHeaders(executionId));
await synchroniser.accept(resource, content);
if (apply) {
await synchroniser.apply(false, createSyncHeaders(generateUuid()));
}
}
async resolveContent(resource: URI): Promise<string | null> {
@@ -399,6 +394,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
throw new UserDataSyncError(e.message, e.code, source);
case UserDataSyncErrorCode.TooManyRequests:
case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter:
case UserDataSyncErrorCode.LocalTooManyRequests:
case UserDataSyncErrorCode.Gone:
case UserDataSyncErrorCode.UpgradeRequired:
@@ -460,21 +456,21 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
return this.previews;
}
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.acceptPreviewContent(resource, content, force, this.syncHeaders));
async accept(resource: URI, content: string | null): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.accept(resource, content));
}
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
if (resource) {
return this.mergeOrAccept(resource, (sychronizer, force) => sychronizer.merge(resource, force, this.syncHeaders));
} else {
return this.mergeAll();
}
async merge(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.merge(resource));
}
private async mergeOrAccept(resource: URI, mergeOrAccept: (synchroniser: IUserDataSynchroniser, force: boolean) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
async discard(resource: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
return this.performAction(resource, sychronizer => sychronizer.discard(resource));
}
private async performAction(resource: URI, action: (synchroniser: IUserDataSynchroniser) => Promise<ISyncResourcePreview | null>): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging or accepting');
throw new Error('Missing preview. Create preview and try again.');
}
const index = this.previews.findIndex(([, preview]) => preview.resourcePreviews.some(({ localResource, previewResource, remoteResource }) =>
@@ -500,9 +496,7 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
}
const synchroniser = this.synchronisers.find(s => s.resource === this.previews![index][0])!;
/* force only if the resource is local or remote resource */
const force = isEqual(resource, resourcePreview.localResource) || isEqual(resource, resourcePreview.remoteResource);
const preview = await mergeOrAccept(synchroniser, force);
const preview = await action(synchroniser);
preview ? this.previews.splice(index, 1, this.toSyncResourcePreview(synchroniser.resource, preview)) : this.previews.splice(index, 1);
const i = this.synchronizingResources.findIndex(s => s[0] === syncResource);
@@ -515,25 +509,33 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
return this.previews;
}
private async mergeAll(): Promise<[SyncResource, ISyncResourcePreview][]> {
async apply(): Promise<[SyncResource, ISyncResourcePreview][]> {
if (!this.previews) {
throw new Error('You need to create preview before merging');
throw new Error('You need to create preview before applying');
}
if (this.synchronizingResources.length) {
throw new Error('Cannot merge while synchronizing resources');
throw new Error('Cannot pull while synchronizing resources');
}
const previews: [SyncResource, ISyncResourcePreview][] = [];
for (const [syncResource, preview] of this.previews) {
this.synchronizingResources.push([syncResource, preview.resourcePreviews.map(r => r.localResource)]);
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
let syncResourcePreview = null;
/* merge those which are not yet merged */
for (const resourcePreview of preview.resourcePreviews) {
syncResourcePreview = await synchroniser.merge(resourcePreview.remoteResource, false, this.syncHeaders);
if ((resourcePreview.localChange !== Change.None || resourcePreview.remoteChange !== Change.None) && resourcePreview.mergeState === MergeState.Preview) {
await synchroniser.merge(resourcePreview.previewResource);
}
}
if (syncResourcePreview) {
previews.push([syncResource, syncResourcePreview]);
/* apply */
const newPreview = await synchroniser.apply(false, this.syncHeaders);
if (newPreview) {
previews.push(this.toSyncResourcePreview(synchroniser.resource, newPreview));
}
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -553,9 +555,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
for (const resourcePreview of preview.resourcePreviews) {
const content = await synchroniser.resolveContent(resourcePreview.remoteResource) || '';
await synchroniser.acceptPreviewContent(resourcePreview.remoteResource, content, true, this.syncHeaders);
const content = await synchroniser.resolveContent(resourcePreview.remoteResource);
await synchroniser.accept(resourcePreview.remoteResource, content);
}
await synchroniser.apply(true, this.syncHeaders);
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -574,9 +577,10 @@ class ManualSyncTask extends Disposable implements IManualSyncTask {
this._onSynchronizeResources.fire(this.synchronizingResources);
const synchroniser = this.synchronisers.find(s => s.resource === syncResource)!;
for (const resourcePreview of preview.resourcePreviews) {
const content = await synchroniser.resolveContent(resourcePreview.localResource) || '';
await synchroniser.acceptPreviewContent(resourcePreview.localResource, content, true, this.syncHeaders);
const content = await synchroniser.resolveContent(resourcePreview.localResource);
await synchroniser.accept(resourcePreview.localResource, content);
}
await synchroniser.apply(true, this.syncHeaders);
this.synchronizingResources.splice(this.synchronizingResources.findIndex(s => s[0] === syncResource), 1);
this._onSynchronizeResources.fire(this.synchronizingResources);
}
@@ -641,8 +645,9 @@ function toStrictResourcePreview(resourcePreview: IResourcePreview): IResourcePr
localResource: resourcePreview.localResource,
previewResource: resourcePreview.previewResource,
remoteResource: resourcePreview.remoteResource,
acceptedResource: resourcePreview.acceptedResource,
localChange: resourcePreview.localChange,
remoteChange: resourcePreview.remoteChange,
merged: resourcePreview.merged,
mergeState: resourcePreview.mergeState,
};
}

View File

@@ -19,7 +19,9 @@ import { assign } from 'vs/base/common/objects';
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';
const DONOT_MAKE_REQUESTS_UNTIL_KEY = 'sync.donot-make-requests-until';
const USER_SESSION_ID_KEY = 'sync.user-session-id';
const MACHINE_SESSION_ID_KEY = 'sync.machine-session-id';
const REQUEST_SESSION_LIMIT = 100;
@@ -40,6 +42,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
private _onTokenSucceed: Emitter<void> = this._register(new Emitter<void>());
readonly onTokenSucceed: Event<void> = this._onTokenSucceed.event;
private _donotMakeRequestsUntil: Date | undefined = undefined;
get donotMakeRequestsUntil() { return this._donotMakeRequestsUntil; }
private _onDidChangeDonotMakeRequestsUntil = this._register(new Emitter<void>());
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
constructor(
@IProductService productService: IProductService,
@IConfigurationService configurationService: IConfigurationService,
@@ -66,12 +73,41 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
/* A requests session that limits requests per sessions */
this.session = new RequestsSession(REQUEST_SESSION_LIMIT, REQUEST_SESSION_INTERVAL, this.requestService, this.logService);
this.initDonotMakeRequestsUntil();
}
setAuthToken(token: string, type: string): void {
this.authToken = { token, type };
}
private initDonotMakeRequestsUntil(): void {
const donotMakeRequestsUntil = this.storageService.getNumber(DONOT_MAKE_REQUESTS_UNTIL_KEY, StorageScope.GLOBAL);
if (donotMakeRequestsUntil && Date.now() < donotMakeRequestsUntil) {
this.setDonotMakeRequestsUntil(new Date(donotMakeRequestsUntil));
}
}
private resetDonotMakeRequestsUntilPromise: CancelablePromise<void> | undefined = undefined;
private setDonotMakeRequestsUntil(donotMakeRequestsUntil: Date | undefined): void {
if (this._donotMakeRequestsUntil?.getTime() !== donotMakeRequestsUntil?.getTime()) {
this._donotMakeRequestsUntil = donotMakeRequestsUntil;
if (this.resetDonotMakeRequestsUntilPromise) {
this.resetDonotMakeRequestsUntilPromise.cancel();
this.resetDonotMakeRequestsUntilPromise = undefined;
}
if (this._donotMakeRequestsUntil) {
this.storageService.store(DONOT_MAKE_REQUESTS_UNTIL_KEY, this._donotMakeRequestsUntil.getTime(), StorageScope.GLOBAL);
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);
}
this._onDidChangeDonotMakeRequestsUntil.fire();
}
}
async getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStore) {
throw new Error('No settings sync store url configured.');
@@ -244,6 +280,11 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
throw new UserDataSyncStoreError('No Auth Token Available', 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);
}
this.setDonotMakeRequestsUntil(undefined);
const commonHeaders = await this.commonHeadersPromise;
options.headers = assign(options.headers || {}, commonHeaders, {
'X-Account-Type': this.authToken.type,
@@ -299,7 +340,13 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
}
if (context.res.statusCode === 429) {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
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);
} else {
throw new UserDataSyncStoreError(`${options.type} request '${options.url?.toString()}' failed because of too many requests (429).`, UserDataSyncErrorCode.TooManyRequests, operationId);
}
}
return context;

View File

@@ -83,4 +83,20 @@ suite('KeybindingsSync', () => {
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
});
test('test apply remote when keybindings file does not exist', async () => {
const fileService = client.instantiationService.get(IFileService);
const keybindingsResource = client.instantiationService.get(IEnvironmentService).keybindingsResource;
if (await fileService.exists(keybindingsResource)) {
await fileService.del(keybindingsResource);
}
const preview = (await testObject.preview(await client.manifest()))!;
server.reset();
const content = await testObject.resolveContent(preview.resourcePreviews[0].remoteResource);
await testObject.accept(preview.resourcePreviews[0].remoteResource, content);
await testObject.apply(false);
assert.deepEqual(server.requests, []);
});
});

View File

@@ -287,7 +287,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
const conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet1, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet1);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -327,7 +328,7 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
let conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
conflicts = testObject.conflicts;
assert.equal(testObject.status, SyncStatus.HasConflicts);
@@ -346,8 +347,9 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
const conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.acceptPreviewContent(conflicts[1].previewResource, tsSnippet1, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
await testObject.accept(conflicts[1].previewResource, tsSnippet1);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -461,7 +463,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet3, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet2);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -565,7 +568,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, htmlSnippet3, false);
await testObject.accept(testObject.conflicts[0].previewResource, htmlSnippet3);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -592,7 +596,8 @@ suite('SnippetsSync', () => {
await updateSnippet('html.json', htmlSnippet2, testClient);
await testObject.sync(await testClient.manifest());
await testObject.acceptPreviewContent(testObject.conflicts[0].previewResource, '', false);
await testObject.accept(testObject.conflicts[0].previewResource, null);
await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.deepEqual(testObject.conflicts, []);
@@ -689,7 +694,8 @@ suite('SnippetsSync', () => {
await testObject.sync(await testClient.manifest());
let conflicts = testObject.conflicts;
await testObject.acceptPreviewContent(conflicts[0].previewResource, htmlSnippet2, false);
await testObject.accept(conflicts[0].previewResource, htmlSnippet2);
await testObject.apply(false);
const fileService = testClient.instantiationService.get(IFileService);
assert.ok(!await fileService.exists(dirname(conflicts[0].previewResource)));
@@ -710,7 +716,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
@@ -736,8 +742,36 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('merge when there are multiple snippets and all snippets are merged and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet2, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.merge(preview!.resourcePreviews[1].localResource);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -762,7 +796,37 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('merge when there are multiple snippets and one snippet has no changes and one snippet is merged and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet1, client2);
await client2.sync();
await updateSnippet('html.json', htmlSnippet1, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].localResource);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -788,7 +852,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.equal(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews,
@@ -821,8 +885,8 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[1].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.merge(preview!.resourcePreviews[1].previewResource);
assert.equal(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews,
@@ -856,7 +920,7 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
@@ -886,8 +950,40 @@ suite('SnippetsSync', () => {
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, htmlSnippet2, false);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[1].previewResource, tsSnippet2, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2);
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
});
test('accept when there are multiple snippets with conflicts and all snippets are accepted and applied', async () => {
const environmentService = testClient.instantiationService.get(IEnvironmentService);
await updateSnippet('html.json', htmlSnippet1, client2);
await updateSnippet('typescript.json', tsSnippet1, client2);
await client2.sync();
await updateSnippet('html.json', htmlSnippet2, testClient);
await updateSnippet('typescript.json', tsSnippet2, testClient);
let preview = await testObject.preview(await testClient.manifest());
assert.equal(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews,
[
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'html.json'),
joinPath(environmentService.userDataSyncHome, testObject.resource, PREVIEW_DIR_NAME, 'typescript.json'),
]);
assert.deepEqual(testObject.conflicts, []);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, htmlSnippet2);
preview = await testObject.accept(preview!.resourcePreviews[1].previewResource, tsSnippet2);
preview = await testObject.apply(false);
assert.equal(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncStoreService, SyncResource, SyncStatus, IUserDataSyncResourceEnablementService, IRemoteUserData, ISyncData, Change, USER_DATA_SYNC_SCHEME, IUserDataManifest, MergeState } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { AbstractSynchroniser, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
@@ -12,6 +12,8 @@ import { Barrier } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
const resource = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'testResource', path: `/current.json` });
@@ -47,27 +49,27 @@ class TestSynchroniser extends AbstractSynchroniser {
}
protected async generatePullPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generatePushPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generateReplacePreview(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IResourcePreview[]> {
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IResourcePreview[]> {
if (this.syncResult.hasError) {
throw new Error('failed');
}
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, previewContent: remoteUserData.ref, previewResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
return [{ localContent: null, localResource: resource, remoteContent: null, remoteResource: resource, acceptedContent: remoteUserData.ref, previewContent: remoteUserData.ref, previewResource: resource, acceptedResource: resource, localChange: Change.Modified, remoteChange: Change.None, hasConflicts: this.syncResult.hasConflicts }];
}
protected async applyPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, preview: IResourcePreview[], forcePush: boolean): Promise<void> {
if (preview[0]?.previewContent) {
await this.applyRef(preview[0].previewContent);
if (preview[0]?.acceptedContent) {
await this.applyRef(preview[0].acceptedContent);
}
}
@@ -106,6 +108,7 @@ suite('TestSynchronizer', () => {
await client.setUp();
userDataSyncStoreService = client.instantiationService.get(IUserDataSyncStoreService);
disposableStore.add(toDisposable(() => userDataSyncStoreService.clear()));
client.instantiationService.get(IFileService).registerProvider(USER_DATA_SYNC_SCHEME, new InMemoryFileSystemProvider());
});
teardown(() => disposableStore.clear());
@@ -276,26 +279,70 @@ suite('TestSynchronizer', () => {
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle after merging if there are no conflicts', async () => {
test('preview: status is syncing after merging if there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Accepted);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle after merging and applying if there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
assertConflicts(testObject.conflicts, []);
});
test('preview: discarding the merge', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.discard(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is syncing after accepting when there are no conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are no conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: false, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
@@ -319,35 +366,80 @@ suite('TestSynchronizer', () => {
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
const preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
let preview = await testObject.preview(await client.manifest());
preview = await testObject.merge(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Conflict);
assertConflicts(testObject.conflicts, [preview!.resourcePreviews[0].previewResource]);
});
test('preview: discarding the conflict', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
const preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource);
await testObject.discard(preview!.resourcePreviews[0].previewResource);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.equal(preview!.resourcePreviews[0].mergeState, MergeState.Preview);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is syncing after accepting when there are conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assert.deepEqual(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are conflicts', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
await testObject.merge(preview!.resourcePreviews[0].previewResource, false);
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
await testObject.merge(preview!.resourcePreviews[0].previewResource);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to syncing after accepting when there are conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
assert.deepEqual(testObject.status, SyncStatus.Syncing);
assertPreviews(preview!.resourcePreviews, [resource]);
assertConflicts(testObject.conflicts, []);
});
test('preview: status is set to idle and sync is applied after accepting when there are conflicts before merging', async () => {
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
testObject.syncResult = { hasConflicts: true, hasError: false };
testObject.syncBarrier.open();
let preview = await testObject.preview(await client.manifest());
preview = await testObject.acceptPreviewContent(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].previewContent!, false);
preview = await testObject.accept(preview!.resourcePreviews[0].previewResource, preview!.resourcePreviews[0].acceptedContent!);
preview = await testObject.apply(false);
assert.deepEqual(testObject.status, SyncStatus.Idle);
assert.equal(preview, null);

View File

@@ -383,4 +383,22 @@ suite('UserDataAutoSyncService', () => {
assert.deepEqual((<UserDataSyncStoreError>e).code, UserDataSyncErrorCode.TooManyRequests);
});
test('test auto sync is suspended when server donot accepts requests', async () => {
const target = new UserDataSyncTestServer(5, 1);
// Set up and sync from the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject: TestUserDataAutoSyncService = testClient.instantiationService.createInstance(TestUserDataAutoSyncService);
while (target.requests.length < 5) {
await testObject.sync();
}
target.reset();
await testObject.sync();
assert.deepEqual(target.requests, []);
});
});

View File

@@ -154,13 +154,13 @@ export class UserDataSyncTestServer implements IRequestService {
get responses(): { status: number }[] { return this._responses; }
reset(): void { this._requests = []; this._responses = []; this._requestsWithAllHeaders = []; }
constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER) { }
constructor(private readonly rateLimit = Number.MAX_SAFE_INTEGER, private readonly retryAfter?: number) { }
async resolveProxy(url: string): Promise<string | undefined> { return url; }
async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
if (this._requests.length === this.rateLimit) {
return this.toResponse(429);
return this.toResponse(429, this.retryAfter ? { 'retry-after': `${this.retryAfter}` } : undefined);
}
const headers: IHeaders = {};
if (options.headers) {

View File

@@ -9,12 +9,13 @@ import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userData
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
import { isWeb } from 'vs/base/common/platform';
import { RequestsSession } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { RequestsSession, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IRequestService } from 'vs/platform/request/common/request';
import { newWriteableBufferStream } from 'vs/base/common/buffer';
import { timeout } from 'vs/base/common/async';
import { NullLogService } from 'vs/platform/log/common/log';
import { Event } from 'vs/base/common/event';
suite('UserDataSyncStoreService', () => {
@@ -322,6 +323,71 @@ suite('UserDataSyncStoreService', () => {
assert.notEqual(target.requestsWithAllHeaders[0].headers!['X-User-Session-Id'], undefined);
});
test('test rate limit on server with retry after', async () => {
const target = new UserDataSyncTestServer(1, 1);
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil);
try {
await testObject.manifest();
assert.fail('should fail');
} catch (e) {
assert.ok(e instanceof UserDataSyncStoreError);
assert.deepEqual((<UserDataSyncStoreError>e).code, UserDataSyncErrorCode.TooManyRequestsAndRetryAfter);
await promise;
assert.ok(!!testObject.donotMakeRequestsUntil);
}
});
test('test donotMakeRequestsUntil is reset after retry time is finished', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
const promise = Event.toPromise(testObject.onDidChangeDonotMakeRequestsUntil);
await timeout(300);
await promise;
assert.ok(!testObject.donotMakeRequestsUntil);
});
test('test donotMakeRequestsUntil is retrieved', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 1)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
const target = client.instantiationService.createInstance(UserDataSyncStoreService);
assert.equal(target.donotMakeRequestsUntil?.getTime(), testObject.donotMakeRequestsUntil?.getTime());
});
test('test donotMakeRequestsUntil is checked and reset after retreived', async () => {
const client = disposableStore.add(new UserDataSyncClient(new UserDataSyncTestServer(1, 0.25)));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest();
try {
await testObject.manifest();
} catch (e) { }
await timeout(300);
const target = client.instantiationService.createInstance(UserDataSyncStoreService);
assert.ok(!target.donotMakeRequestsUntil);
});
});
suite('UserDataSyncRequestsSession', () => {

View File

@@ -106,7 +106,6 @@ function normalizeRequestPath(requestUri: URI) {
return `${scheme}://`; // Url has own authority.
}
}));
console.log(requestUri, resourceUri);
return resourceUri.with({
query: requestUri.query,
fragment: requestUri.fragment

View File

@@ -174,7 +174,7 @@ export class WebviewProtocolProvider extends Disposable {
const fileService = {
readFileStream: async (resource: URI): Promise<VSBufferReadableStream> => {
if (uri.scheme === Schemas.file) {
if (resource.scheme === Schemas.file) {
return (await this.fileService.readFileStream(resource)).value;
}