提交 322bd608 编写于 作者: S Sandeep Somavarapu

move auto sync logic from sync service to auto sync service

上级 836bde41
......@@ -6,7 +6,7 @@
import { Delayer, disposableTimeout, CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, toDisposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService, UserDataAutoSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isPromiseCanceledError } from 'vs/base/common/errors';
......@@ -17,8 +17,6 @@ import { IUserDataSyncMachine, IUserDataSyncMachinesService } from 'vs/platform/
import { PlatformToString, isWeb, Platform, platform } from 'vs/base/common/platform';
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { IProductService } from 'vs/platform/product/common/productService';
import { IHeaders } from 'vs/base/parts/request/common/request';
import { generateUuid } from 'vs/base/common/uuid';
import { localize } from 'vs/nls';
type AutoSyncClassification = {
......@@ -29,6 +27,10 @@ type AutoSyncEnablementClassification = {
enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
type AutoSyncErrorClassification = {
code: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
const enablementKey = 'sync.enable';
const disableMachineEventuallyKey = 'sync.disableMachineEventually';
const SESSION_ID_KEY = 'sync.sessionId';
......@@ -83,11 +85,11 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
readonly onError: Event<UserDataSyncError> = this._onError.event;
constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataSyncAccountService private readonly authTokenService: IUserDataSyncAccountService,
@IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
@IProductService private readonly productService: IProductService,
......@@ -99,15 +101,10 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
if (userDataSyncStoreService.userDataSyncStore) {
this.updateAutoSync();
// Update machine if sync is enabled
if (this.isEnabled()) {
this.updateMachine(true);
} else if (this.hasToDisableMachineEventually()) {
if (this.hasToDisableMachineEventually()) {
this.disableMachineEventually();
}
this._register(authTokenService.onDidChangeAccount(() => this.updateAutoSync()));
this._register(userDataSyncAccountService.onDidChangeAccount(() => 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)));
}
......@@ -117,7 +114,7 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
const { enabled, reason } = this.isAutoSyncEnabled();
if (enabled) {
if (this.autoSync.value === undefined) {
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncService, this.logService);
this.autoSync.value = new AutoSync(1000 * 60 * 5 /* 5 miutes */, this.userDataSyncStoreService, this.userDataSyncService, this.userDataSyncMachinesService, this.logService, this.storageService, this.productService);
this.autoSync.value.register(this.autoSync.value.onDidStartSync(() => this.lastSyncTriggerTime = new Date().getTime()));
this.autoSync.value.register(this.autoSync.value.onDidFinishSync(e => this.onDidFinishSync(e)));
if (this.startAutoSync()) {
......@@ -140,31 +137,39 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
if (!this.isEnabled()) {
return { enabled: false, reason: 'sync is disabled' };
}
if (!this.authTokenService.account) {
if (!this.userDataSyncAccountService.account) {
return { enabled: false, reason: 'token is not avaialable' };
}
return { enabled: true };
}
async turnOn(pullFirst: boolean): Promise<void> {
await this.updateMachine(true);
this.stopDisableMachineEventually();
if (pullFirst) {
await this.userDataSyncService.pull();
} else {
await this.userDataSyncService.sync(CancellationToken.None);
await this.userDataSyncService.sync();
}
this.setEnablement(true);
}
async turnOff(everywhere: boolean, softTurnOffOnError?: boolean, donotDisableMachine?: boolean): Promise<void> {
async turnOff(everywhere: boolean, softTurnOffOnError?: boolean, donotRemoveMachine?: boolean): Promise<void> {
try {
if (!donotDisableMachine) {
await this.updateMachine(false);
// Remove machine
if (!donotRemoveMachine) {
await this.userDataSyncMachinesService.removeCurrentMachine();
}
// Disable Auto Sync
this.setEnablement(false);
// Reset Session
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
// Reset
if (everywhere) {
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
await this.userDataSyncService.reset();
......@@ -189,44 +194,6 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
}
}
private async updateMachine(enable: boolean): Promise<void> {
if (!this.authTokenService.account) {
return;
}
const machines = await this.userDataSyncMachinesService.getMachines();
const currentMachine = machines.find(machine => machine.isCurrent);
if (enable) {
this.stopDisableMachineEventually();
// Add or enable current machine
if (!currentMachine) {
const name = this.computeDefaultMachineName(machines);
await this.userDataSyncMachinesService.addCurrentMachine(name);
this.logService.debug('Auto Sync: Added current machine to sync');
} else if (currentMachine.disabled) {
await this.userDataSyncMachinesService.setEnablement(currentMachine.id, true);
this.logService.debug('Auto Sync: Enabled current machine to sync');
}
} else if (currentMachine && !currentMachine.disabled) {
await this.userDataSyncMachinesService.setEnablement(currentMachine.id, false);
this.logService.debug('Auto Sync: Disabled current machine to sync');
}
}
private computeDefaultMachineName(machines: IUserDataSyncMachine[]): string {
const namePrefix = `${this.productService.nameLong} (${PlatformToString(isWeb ? Platform.Web : platform)})`;
const nameRegEx = new RegExp(`${escapeRegExpCharacters(namePrefix)}\\s#(\\d)`);
let nameIndex = 0;
for (const machine of machines) {
const matches = nameRegEx.exec(machine.name);
const index = matches ? parseInt(matches[1]) : 0;
nameIndex = index > nameIndex ? index : nameIndex;
}
return `${namePrefix} #${nameIndex + 1}`;
}
private async onDidFinishSync(error: Error | undefined): Promise<void> {
if (!error) {
// Sync finished without errors
......@@ -236,18 +203,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
// Error while syncing
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
// Log to telemetry
if (userDataSyncError instanceof UserDataAutoSyncError) {
this.telemetryService.publicLog2<{ code: string }, AutoSyncErrorClassification>(`autosync/error`, { code: userDataSyncError.code });
}
// Turned off from another device or session got expired
if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) {
await this.turnOff(false, true /* force soft turnoff on error */);
this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud');
} else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests || userDataSyncError.code === UserDataSyncErrorCode.TooManyRequests) {
}
// Exceeded Rate Limit
else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests || userDataSyncError.code === UserDataSyncErrorCode.TooManyRequests) {
await this.turnOff(false, true /* force soft turnoff on error */,
true /* do not disable machine because disabling a machine makes request to server and can fail with TooManyRequests */);
this.disableMachineEventually();
this.logService.info('Auto Sync: Turned off sync because of making too many requests to server');
} else {
}
else {
this.logService.error(userDataSyncError);
this.successiveFailures++;
}
this._onError.fire(userDataSyncError);
}
......@@ -263,8 +243,8 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
this.stopDisableMachineEventually();
// disable only if sync is disabled
if (!this.isEnabled()) {
return this.updateMachine(false);
if (!this.isEnabled() && this.userDataSyncAccountService.account) {
await this.userDataSyncMachinesService.removeCurrentMachine();
}
}
......@@ -324,12 +304,12 @@ class AutoSync extends Disposable {
constructor(
private readonly interval: number /* in milliseconds */,
private readonly userDataSyncStoreService: IUserDataSyncStoreService,
private readonly userDataSyncService: IUserDataSyncService,
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
private readonly logService: IUserDataSyncLogService,
private readonly userDataSyncStoreService: IUserDataSyncStoreService,
private readonly telemetryService: ITelemetryService,
private readonly storageService: IStorageService,
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
private readonly productService: IProductService,
) {
super();
}
......@@ -379,30 +359,19 @@ class AutoSync extends Disposable {
this._onDidStartSync.fire();
let error: Error | undefined;
try {
await this.userDataSyncService.sync(token);
} catch (e) {
this.logService.error(e);
error = e;
}
this._onDidFinishSync.fire(error);
}
private async doSync(reason: string, token: CancellationToken): Promise<void> {
try {
this.telemetryService.publicLog2('sync/getmanifest');
const syncHeaders: IHeaders = { 'X-Execution-Id': generateUuid() };
let manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
const syncTask = await this.userDataSyncService.createSyncTask();
let manifest = syncTask.manifest;
// Server has no data but this machine was synced before
if (manifest === null && await this.userDataSyncService.hasPreviouslySynced()) {
// Sync was turned off in the cloud
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
throw new UserDataAutoSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
}
const sessionId = this.storageService.get(SESSION_ID_KEY, StorageScope.GLOBAL);
// Server session is different from client session
if (sessionId && manifest && sessionId !== manifest.session) {
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
throw new UserDataAutoSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
}
const machines = await this.userDataSyncMachinesService.getMachines(manifest || undefined);
......@@ -415,14 +384,19 @@ class AutoSync extends Disposable {
// Check if sync was turned off from other machine
if (currentMachine?.disabled) {
// Throw TurnedOff error
throw new UserDataSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
throw new UserDataAutoSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
}
await this.userDataSyncService.sync(manifest, headers, token);
await syncTask.run(token);
// After syncing, get the manifest if it was not available before
if (manifest === null) {
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
manifest = await this.userDataSyncStoreService.manifest();
}
// Update local session id
if (manifest && manifest.session !== sessionId) {
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
}
// Return if cancellation is requested
......@@ -430,20 +404,32 @@ class AutoSync extends Disposable {
return;
}
// Update local session id
if (manifest && manifest.session !== sessionId) {
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
// Add current machine
if (!currentMachine) {
const name = this.computeDefaultMachineName(machines);
await this.userDataSyncMachinesService.addCurrentMachine(name, manifest || undefined);
}
} catch (error) {
if (error instanceof UserDataSyncError) {
this.telemetryService.publicLog2<{ resource?: string }, SyncClassification>(`sync/error/${error.code}`, { resource: error.resource });
}
throw error;
} finally {
this.updateStatus();
this._onSyncErrors.fire(this._syncErrors);
} catch (e) {
this.logService.error(e);
error = e;
}
this._onDidFinishSync.fire(error);
}
private computeDefaultMachineName(machines: IUserDataSyncMachine[]): string {
const namePrefix = `${this.productService.nameLong} (${PlatformToString(isWeb ? Platform.Web : platform)})`;
const nameRegEx = new RegExp(`${escapeRegExpCharacters(namePrefix)}\\s#(\\d)`);
let nameIndex = 0;
for (const machine of machines) {
const matches = nameRegEx.exec(machine.name);
const index = matches ? parseInt(matches[1]) : 0;
nameIndex = index > nameIndex ? index : nameIndex;
}
return `${namePrefix} #${nameIndex + 1}`;
}
register<T extends IDisposable>(t: T): T {
......
......@@ -224,7 +224,7 @@ export class UserDataSyncError extends Error {
}
static toUserDataSyncError(error: Error): UserDataSyncError {
if (error instanceof UserDataSyncStoreError) {
if (error instanceof UserDataSyncError) {
return error;
}
const match = /^(.+) \(UserDataSyncError\) (.+)?$/.exec(error.name);
......@@ -242,6 +242,12 @@ export class UserDataSyncStoreError extends UserDataSyncError {
}
}
export class UserDataAutoSyncError extends UserDataSyncError {
constructor(message: string, code: UserDataSyncErrorCode) {
super(message, code);
}
}
//#endregion
// #region User Data Synchroniser
......@@ -340,6 +346,11 @@ export interface IUserDataSyncResourceEnablementService {
export type SyncResourceConflicts = { syncResource: SyncResource, conflicts: Conflict[] };
export interface ISyncTask {
manifest: IUserDataManifest | null;
run(token: CancellationToken): Promise<void>;
}
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
export interface IUserDataSyncService {
_serviceBrand: any;
......@@ -357,12 +368,14 @@ export interface IUserDataSyncService {
readonly onDidChangeLastSyncTime: Event<number>;
pull(): Promise<void>;
sync(token: CancellationToken): Promise<void>;
sync(): Promise<void>;
stop(): Promise<void>;
replace(uri: URI): Promise<void>;
reset(): Promise<void>;
resetLocal(): Promise<void>;
createSyncTask(): Promise<ISyncTask>
isFirstTimeSyncingWithAnotherMachine(): Promise<boolean>;
hasPreviouslySynced(): Promise<boolean>;
resolveContent(resource: URI): Promise<string | null>;
......
......@@ -14,7 +14,6 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { CancellationToken } from 'vs/base/common/cancellation';
export class UserDataSyncChannel implements IServerChannel {
......@@ -45,11 +44,12 @@ export class UserDataSyncChannel implements IServerChannel {
switch (command) {
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflicts, this.service.lastSyncTime]);
case 'pull': return this.service.pull();
case 'sync': return this.service.sync(CancellationToken.None);
case 'sync': return this.service.sync();
case 'stop': this.service.stop(); return Promise.resolve();
case 'replace': return this.service.replace(URI.revive(args[0]));
case 'reset': return this.service.reset();
case 'resetLocal': return this.service.resetLocal();
case 'hasPreviouslySynced': return this.service.hasPreviouslySynced();
case 'isFirstTimeSyncingWithAnotherMachine': return this.service.isFirstTimeSyncingWithAnotherMachine();
case 'acceptConflict': return this.service.acceptConflict(URI.revive(args[0]), args[1]);
case 'resolveContent': return this.service.resolveContent(URI.revive(args[0]));
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncResource, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncErrorCode, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle, IUserDataManifest, ISyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
......@@ -13,16 +13,14 @@ import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalS
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { equals } from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { isEqual } from 'vs/base/common/resources';
import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync';
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { CancellationToken } from 'vs/base/common/cancellation';
import { generateUuid } from 'vs/base/common/uuid';
import { IHeaders } from 'vs/base/parts/request/common/request';
import { generateUuid } from 'vs/base/common/uuid';
type SyncClassification = {
resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
......@@ -69,7 +67,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService,
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
) {
super();
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
......@@ -128,7 +125,30 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
private recoveredSettings: boolean = false;
async sync(token: CancellationToken): Promise<void> {
async sync(): Promise<void> {
const syncTask = await this.createSyncTask();
return syncTask.run(CancellationToken.None);
}
async createSyncTask(): Promise<ISyncTask> {
this.telemetryService.publicLog2('sync/getmanifest');
const syncHeaders: IHeaders = { 'X-Execution-Id': generateUuid() };
const manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
let executed = false;
const that = this;
return {
manifest,
run(token: CancellationToken): Promise<void> {
if (executed) {
throw new Error('Can run a task only once');
}
return that.doSync(manifest, syncHeaders, token);
}
};
}
private async doSync(manifest: IUserDataManifest | null, syncHeaders: IHeaders, token: CancellationToken): Promise<void> {
await this.checkEnablement();
if (!this.recoveredSettings) {
......@@ -141,10 +161,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return;
}
return this.doSync(token);
}
private async doSync(token: CancellationToken): Promise<void> {
const startTime = new Date().getTime();
this._syncErrors = [];
try {
......@@ -153,35 +169,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this.setStatus(SyncStatus.Syncing);
}
this.telemetryService.publicLog2('sync/getmanifest');
const syncHeaders: IHeaders = { 'X-Execution-Id': generateUuid() };
let manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
// Server has no data but this machine was synced before
if (manifest === null && await this.hasPreviouslySynced()) {
// Sync was turned off in the cloud
throw new UserDataSyncError(localize('turned off', "Cannot sync because syncing is turned off in the cloud"), UserDataSyncErrorCode.TurnedOff);
}
const sessionId = this.storageService.get(SESSION_ID_KEY, StorageScope.GLOBAL);
// Server session is different from client session
if (sessionId && manifest && sessionId !== manifest.session) {
throw new UserDataSyncError(localize('session expired', "Cannot sync because current session is expired"), UserDataSyncErrorCode.SessionExpired);
}
const machines = await this.userDataSyncMachinesService.getMachines(manifest || undefined);
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
const currentMachine = machines.find(machine => machine.isCurrent);
// Check if sync was turned off from other machine
if (currentMachine?.disabled) {
// Throw TurnedOff error
throw new UserDataSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
}
for (const synchroniser of this.synchronisers) {
// Return if cancellation is requested
if (token.isCancellationRequested) {
......@@ -195,34 +182,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
// After syncing, get the manifest if it was not available before
if (manifest === null) {
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
}
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
// Update local session id
if (manifest && manifest.session !== sessionId) {
this.storageService.store(SESSION_ID_KEY, manifest.session, StorageScope.GLOBAL);
}
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
// Return if cancellation is requested
if (token.isCancellationRequested) {
return;
}
this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`);
this.updateLastSyncTime();
} catch (error) {
if (error instanceof UserDataSyncError) {
this.telemetryService.publicLog2<{ resource?: string }, SyncClassification>(`sync/error/${error.code}`, { resource: error.resource });
......@@ -337,7 +298,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async resetLocal(): Promise<void> {
await this.checkEnablement();
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
for (const synchroniser of this.synchronisers) {
try {
......
......@@ -7,12 +7,20 @@ import * as assert from 'assert';
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
import { IUserDataSyncService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IUserDataSyncService, SyncResource, UserDataAutoSyncError, UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { VSBuffer } from 'vs/base/common/buffer';
import { joinPath } from 'vs/base/common/resources';
class TestUserDataAutoSyncService extends UserDataAutoSyncService {
protected startAutoSync(): boolean { return false; }
protected getSyncTriggerDelayTime(): number { return 50; }
sync(): Promise<void> {
return this.triggerSync(['sync'], false);
}
}
suite('UserDataAutoSyncService', () => {
......@@ -28,7 +36,7 @@ suite('UserDataAutoSyncService', () => {
await client.setUp();
// Sync once and reset requests
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
target.reset();
const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
......@@ -50,7 +58,7 @@ suite('UserDataAutoSyncService', () => {
await client.setUp();
// Sync once and reset requests
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
target.reset();
const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
......@@ -76,7 +84,7 @@ suite('UserDataAutoSyncService', () => {
await client.setUp();
// Sync once and reset requests
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
target.reset();
const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
......@@ -98,7 +106,7 @@ suite('UserDataAutoSyncService', () => {
await client.setUp();
// Sync once and reset requests
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
target.reset();
const testObject: UserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
......@@ -115,5 +123,209 @@ suite('UserDataAutoSyncService', () => {
assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
});
test('test first auto sync requests', async () => {
// Setup the client
const target = new UserDataSyncTestServer();
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machines
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} },
// Settings
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } },
// Keybindings
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '0' } },
// Snippets
{ type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '0' } },
// Global state
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machines
{ type: 'POST', url: `${target.url}/v1/resource/machines`, headers: { 'If-Match': '0' } }
]);
});
test('test further auto sync requests without changes', async () => {
// Setup the client
const target = new UserDataSyncTestServer();
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
// Sync once and reset requests
await testObject.sync();
target.reset();
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }
]);
});
test('test further auto sync requests with changes', async () => {
// Setup the client
const target = new UserDataSyncTestServer();
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
// Sync once and reset requests
await testObject.sync();
target.reset();
// Do changes in the client
const fileService = client.instantiationService.get(IFileService);
const environmentService = client.instantiationService.get(IEnvironmentService);
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{}`));
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Settings
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } },
// Keybindings
{ type: 'POST', url: `${target.url}/v1/resource/keybindings`, headers: { 'If-Match': '1' } },
// Snippets
{ type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } },
// Global state
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } },
]);
});
test('test auto sync send execution id header', async () => {
// Setup the client
const target = new UserDataSyncTestServer();
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject: TestUserDataAutoSyncService = client.instantiationService.createInstance(TestUserDataAutoSyncService);
// Sync once and reset requests
await testObject.sync();
target.reset();
await testObject.sync();
for (const request of target.requestsWithAllHeaders) {
const hasExecutionIdHeader = request.headers && request.headers['X-Execution-Id'] && request.headers['X-Execution-Id'].length > 0;
if (request.url.startsWith(`${target.url}/v1/resource/machines`)) {
assert.ok(!hasExecutionIdHeader, `Should not have execution header: ${request.url}`);
} else {
assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`);
}
}
});
test('test delete on one client throws turned off error on other client while syncing', async () => {
const target = new UserDataSyncTestServer();
// Set up and sync from the client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync();
// 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);
await testObject.sync();
// Reset from the first client
await client.instantiationService.get(IUserDataSyncService).reset();
// Sync from the test client
target.reset();
const errorPromise = Event.toPromise(testObject.onError);
await testObject.sync();
const e = await errorPromise;
assert.ok(e instanceof UserDataAutoSyncError);
assert.deepEqual((<UserDataAutoSyncError>e).code, UserDataSyncErrorCode.TurnedOff);
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machine
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } },
]);
});
test('test creating new session from one client throws session expired error on another client while syncing', async () => {
const target = new UserDataSyncTestServer();
// Set up and sync from the client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync();
// 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);
await testObject.sync();
// Reset from the first client
await client.instantiationService.get(IUserDataSyncService).reset();
// Sync again from the first client to create new session
await client.instantiationService.get(IUserDataSyncService).sync();
// Sync from the test client
target.reset();
const errorPromise = Event.toPromise(testObject.onError);
await testObject.sync();
const e = await errorPromise;
assert.ok(e instanceof UserDataAutoSyncError);
assert.deepEqual((<UserDataAutoSyncError>e).code, UserDataSyncErrorCode.SessionExpired);
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machine
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: { 'If-None-Match': '1' } },
]);
});
test('test rate limit on server', async () => {
const target = new UserDataSyncTestServer(5);
// 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);
const errorPromise = Event.toPromise(testObject.onError);
while (target.requests.length < 5) {
await testObject.sync();
}
const e = await errorPromise;
assert.ok(e instanceof UserDataSyncStoreError);
assert.deepEqual((<UserDataSyncStoreError>e).code, UserDataSyncErrorCode.TooManyRequests);
});
});
......@@ -121,7 +121,7 @@ export class UserDataSyncClient extends Disposable {
}
sync(): Promise<void> {
return this.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
return this.instantiationService.get(IUserDataSyncService).sync();
}
read(resource: SyncResource): Promise<IUserData> {
......@@ -154,9 +154,14 @@ 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) { }
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);
}
const headers: IHeaders = {};
if (options.headers) {
if (options.headers['If-None-Match']) {
......
......@@ -4,14 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { IUserDataSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { VSBuffer } from 'vs/base/common/buffer';
import { joinPath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
suite('UserDataSyncService', () => {
......@@ -27,13 +26,11 @@ suite('UserDataSyncService', () => {
const testObject = client.instantiationService.get(IUserDataSyncService);
// Sync for first time
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machines
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} },
// Settings
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '0' } },
......@@ -48,8 +45,6 @@ suite('UserDataSyncService', () => {
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
});
......@@ -62,13 +57,11 @@ suite('UserDataSyncService', () => {
const testObject = client.instantiationService.get(IUserDataSyncService);
// Sync for first time
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
// Machines
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} },
// Settings
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
// Keybindings
......@@ -79,8 +72,6 @@ suite('UserDataSyncService', () => {
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
// Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
});
......@@ -91,7 +82,7 @@ suite('UserDataSyncService', () => {
// Setup and sync from the first client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -126,7 +117,7 @@ suite('UserDataSyncService', () => {
// Setup and sync from the first client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client with changes
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -163,7 +154,7 @@ suite('UserDataSyncService', () => {
// Setup and sync from the first client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -173,7 +164,7 @@ suite('UserDataSyncService', () => {
// Sync (merge) from the test client
target.reset();
await testObject.isFirstTimeSyncingWithAnotherMachine();
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
/* first time sync */
......@@ -184,7 +175,6 @@ suite('UserDataSyncService', () => {
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
/* sync */
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/snippets/latest`, headers: {} },
......@@ -200,7 +190,7 @@ suite('UserDataSyncService', () => {
// Setup and sync from the first client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client with changes
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -216,7 +206,7 @@ suite('UserDataSyncService', () => {
// Sync (merge) from the test client
target.reset();
await testObject.isFirstTimeSyncingWithAnotherMachine();
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
/* first time sync */
......@@ -225,7 +215,6 @@ suite('UserDataSyncService', () => {
/* first time sync */
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/machines/latest`, headers: {} },
{ type: 'GET', url: `${target.url}/v1/resource/settings/latest`, headers: {} },
{ type: 'POST', url: `${target.url}/v1/resource/settings`, headers: { 'If-Match': '1' } },
{ type: 'GET', url: `${target.url}/v1/resource/keybindings/latest`, headers: {} },
......@@ -245,11 +234,11 @@ suite('UserDataSyncService', () => {
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// sync from the client again
target.reset();
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
......@@ -264,7 +253,7 @@ suite('UserDataSyncService', () => {
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
target.reset();
// Do changes in the client
......@@ -276,7 +265,7 @@ suite('UserDataSyncService', () => {
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
// Sync from the client
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
......@@ -298,13 +287,13 @@ suite('UserDataSyncService', () => {
// Sync from first client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Sync from test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// Do changes in first client and sync
const fileService = client.instantiationService.get(IFileService);
......@@ -313,11 +302,11 @@ suite('UserDataSyncService', () => {
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
await fileService.writeFile(joinPath(environmentService.snippetsHome, 'html.json'), VSBuffer.fromString(`{ "a": "changed" }`));
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'de' })));
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Sync from test client
target.reset();
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
......@@ -341,7 +330,7 @@ suite('UserDataSyncService', () => {
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// Reset from the client
target.reset();
......@@ -361,14 +350,14 @@ suite('UserDataSyncService', () => {
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// Reset from the client
await testObject.reset();
// Sync again
target.reset();
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(target.requests, [
// Manifest
......@@ -387,81 +376,10 @@ suite('UserDataSyncService', () => {
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '0' } },
// Extensions
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
});
test('test delete on one client throws turned off error on other client while syncing', async () => {
const target = new UserDataSyncTestServer();
// Set up and sync from the client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
// Set up and sync from the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
// Reset from the first client
await client.instantiationService.get(IUserDataSyncService).reset();
// Sync from the test client
target.reset();
try {
await testObject.sync(CancellationToken.None);
} catch (e) {
assert.ok(e instanceof UserDataSyncError);
assert.deepEqual((<UserDataSyncError>e).code, UserDataSyncErrorCode.TurnedOff);
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
return;
}
throw assert.fail('Should fail with turned off error');
});
test('test creating new session from one client throws session expired error on another client while syncing', async () => {
const target = new UserDataSyncTestServer();
// Set up and sync from the client
const client = disposableStore.add(new UserDataSyncClient(target));
await client.setUp();
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
// Set up and sync from the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
await testClient.setUp();
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
// Reset from the first client
await client.instantiationService.get(IUserDataSyncService).reset();
// Sync again from the first client to create new session
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
// Sync from the test client
target.reset();
try {
await testObject.sync(CancellationToken.None);
} catch (e) {
assert.ok(e instanceof UserDataSyncError);
assert.deepEqual((<UserDataSyncError>e).code, UserDataSyncErrorCode.SessionExpired);
assert.deepEqual(target.requests, [
// Manifest
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
]);
return;
}
throw assert.fail('Should fail with turned off error');
});
test('test sync status', async () => {
const target = new UserDataSyncTestServer();
......@@ -473,7 +391,7 @@ suite('UserDataSyncService', () => {
// sync from the client
const actualStatuses: SyncStatus[] = [];
const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status));
await testObject.sync(CancellationToken.None);
await testObject.sync();
disposable.dispose();
assert.deepEqual(actualStatuses, [SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle, SyncStatus.Syncing, SyncStatus.Idle]);
......@@ -488,7 +406,7 @@ suite('UserDataSyncService', () => {
let fileService = client.instantiationService.get(IFileService);
let environmentService = client.instantiationService.get(IEnvironmentService);
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -499,7 +417,7 @@ suite('UserDataSyncService', () => {
const testObject = testClient.instantiationService.get(IUserDataSyncService);
// sync from the client
await testObject.sync(CancellationToken.None);
await testObject.sync();
assert.deepEqual(testObject.status, SyncStatus.HasConflicts);
assert.deepEqual(testObject.conflicts.map(({ syncResource }) => syncResource), [SyncResource.Settings]);
......@@ -514,7 +432,7 @@ suite('UserDataSyncService', () => {
let fileService = client.instantiationService.get(IFileService);
let environmentService = client.instantiationService.get(IEnvironmentService);
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client and get conflicts in settings
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -523,17 +441,17 @@ suite('UserDataSyncService', () => {
let testEnvironmentService = testClient.instantiationService.get(IEnvironmentService);
await testFileService.writeFile(testEnvironmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 })));
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// sync from the first client with changes in keybindings
await fileService.writeFile(environmentService.keybindingsResource, VSBuffer.fromString(JSON.stringify([{ 'command': 'abcd', 'key': 'cmd+c' }])));
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// sync from the test client
target.reset();
const actualStatuses: SyncStatus[] = [];
const disposable = testObject.onDidChangeStatus(status => actualStatuses.push(status));
await testObject.sync(CancellationToken.None);
await testObject.sync();
disposable.dispose();
assert.deepEqual(actualStatuses, []);
......@@ -556,7 +474,7 @@ suite('UserDataSyncService', () => {
let fileService = client.instantiationService.get(IFileService);
let environmentService = client.instantiationService.get(IEnvironmentService);
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 14 })));
await client.instantiationService.get(IUserDataSyncService).sync(CancellationToken.None);
await client.instantiationService.get(IUserDataSyncService).sync();
// Setup the test client
const testClient = disposableStore.add(new UserDataSyncClient(target));
......@@ -565,7 +483,7 @@ suite('UserDataSyncService', () => {
environmentService = testClient.instantiationService.get(IEnvironmentService);
await fileService.writeFile(environmentService.settingsResource, VSBuffer.fromString(JSON.stringify({ 'editor.fontSize': 16 })));
const testObject = testClient.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
// sync from the client
await testObject.stop();
......@@ -581,15 +499,11 @@ suite('UserDataSyncService', () => {
await client.setUp();
const testObject = client.instantiationService.get(IUserDataSyncService);
await testObject.sync(CancellationToken.None);
await testObject.sync();
for (const request of target.requestsWithAllHeaders) {
const hasExecutionIdHeader = request.headers && request.headers['X-Execution-Id'] && request.headers['X-Execution-Id'].length > 0;
if (request.url.startsWith(`${target.url}/v1/resource/machines`)) {
assert.ok(!hasExecutionIdHeader, `Should not have execution header: ${request.url}`);
} else {
assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`);
}
assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`);
}
});
......
......@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle, ISyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
......@@ -73,6 +73,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('sync');
}
createSyncTask(): Promise<ISyncTask> {
throw new Error('not supported');
}
stop(): Promise<void> {
return this.channel.call('stop');
}
......@@ -89,6 +93,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('resetLocal');
}
hasPreviouslySynced(): Promise<boolean> {
return this.channel.call('hasPreviouslySynced');
}
isFirstTimeSyncingWithAnotherMachine(): Promise<boolean> {
return this.channel.call('isFirstTimeSyncingWithAnotherMachine');
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册