Skip to content

Commit a27c9fc

Browse files
committed
1 parent ad063b9 commit a27c9fc

10 files changed

Lines changed: 110 additions & 43 deletions

File tree

src/vs/platform/userDataSync/common/extensionsSync.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
6+
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
77
import { VSBuffer } from 'vs/base/common/buffer';
88
import { Emitter, Event } from 'vs/base/common/event';
99
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -154,7 +154,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
154154
await this.apply(previewResult);
155155
} catch (e) {
156156
this.setStatus(SyncStatus.Idle);
157-
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
157+
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
158158
// Rejected as there is a new remote version. Syncing again,
159159
this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...');
160160
return this.sync();
@@ -345,12 +345,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
345345
}
346346

347347
private getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
348-
return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null);
348+
return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null, this.source);
349349
}
350350

351351
private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise<IUserData> {
352352
const content = JSON.stringify(extensions);
353-
ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref);
353+
ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref, this.source);
354354
return { content, ref };
355355
}
356356

src/vs/platform/userDataSync/common/globalStateSync.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
6+
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
77
import { VSBuffer } from 'vs/base/common/buffer';
88
import { Emitter, Event } from 'vs/base/common/event';
99
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
132132
this.logService.trace('UI State: Finished synchronizing ui state.');
133133
} catch (e) {
134134
this.setStatus(SyncStatus.Idle);
135-
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
135+
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
136136
// Rejected as there is a new remote version. Syncing again,
137137
this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...');
138138
return this.sync();
@@ -259,12 +259,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
259259
}
260260

261261
private getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
262-
return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null);
262+
return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null, this.source);
263263
}
264264

265265
private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise<IUserData> {
266266
const content = JSON.stringify(globalState);
267-
ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref);
267+
ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref, this.source);
268268
return { content, ref };
269269
}
270270

src/vs/platform/userDataSync/common/keybindingsSync.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
7-
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
7+
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync';
88
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
99
import { VSBuffer } from 'vs/base/common/buffer';
1010
import { parse, ParseError } from 'vs/base/common/json';
@@ -266,7 +266,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs
266266
} catch (e) {
267267
this.syncPreviewResultPromise = null;
268268
this.setStatus(SyncStatus.Idle);
269-
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
269+
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
270270
// Rejected as there is a new remote version. Syncing again,
271271
this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...');
272272
return this.sync();
@@ -433,11 +433,11 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs
433433
}
434434

435435
private async getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
436-
return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null);
436+
return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null, this.source);
437437
}
438438

439439
private async updateRemoteUserData(content: string, ref: string | null): Promise<string> {
440-
return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref);
440+
return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref, this.source);
441441
}
442442

443443
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {

src/vs/platform/userDataSync/common/settingsSync.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
7-
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
7+
import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
88
import { VSBuffer } from 'vs/base/common/buffer';
99
import { parse, ParseError } from 'vs/base/common/json';
1010
import { localize } from 'vs/nls';
@@ -302,7 +302,7 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti
302302
} catch (e) {
303303
this.syncPreviewResultPromise = null;
304304
this.setStatus(SyncStatus.Idle);
305-
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
305+
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) {
306306
// Rejected as there is a new remote version. Syncing again,
307307
this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...');
308308
return this.sync();
@@ -454,11 +454,11 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti
454454
}
455455

456456
private getRemoteUserData(lastSyncData?: IUserData | null): Promise<IUserData> {
457-
return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null);
457+
return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null, this.source);
458458
}
459459

460460
private async writeToRemote(content: string, ref: string | null): Promise<string> {
461-
return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref);
461+
return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref, this.source);
462462
}
463463

464464
private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise<void> {

src/vs/platform/userDataSync/common/userDataAutoSync.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { timeout } from 'vs/base/common/async';
7-
import { Event } from 'vs/base/common/event';
7+
import { Event, Emitter } from 'vs/base/common/event';
88
import { Disposable } from 'vs/base/common/lifecycle';
99
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
10-
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
10+
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
1111

1212
export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncService {
1313

1414
_serviceBrand: any;
1515

1616
private enabled: boolean = false;
17+
private successiveFailures: number = 0;
18+
19+
private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>());
20+
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event;
1721

1822
constructor(
1923
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -41,6 +45,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
4145
this.sync(true, auto);
4246
return;
4347
} else {
48+
this.successiveFailures = 0;
4449
if (stopIfDisabled) {
4550
this.userDataSyncService.stop();
4651
this.logService.info('Auto sync stopped.');
@@ -65,11 +70,17 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer
6570
}
6671
}
6772
await this.userDataSyncService.sync();
73+
this.successiveFailures = 0;
6874
} catch (e) {
75+
this.successiveFailures++;
6976
this.logService.error(e);
77+
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown });
78+
}
79+
if (this.successiveFailures > 5) {
80+
this._onError.fire({ code: UserDataSyncErrorCode.TooManyFailures });
7081
}
7182
if (loop) {
72-
await timeout(1000 * 60 * 5); // Loop sync for every 5 min.
83+
await timeout(1000 * 60 * 5 * (this.successiveFailures + 1)); // Loop sync for every (successive failures count + 1) times 5 mins interval.
7384
this.sync(loop, true);
7485
}
7586
}

src/vs/platform/userDataSync/common/userDataSync.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,18 @@ export interface IUserData {
123123
content: string | null;
124124
}
125125

126-
export enum UserDataSyncStoreErrorCode {
126+
export enum UserDataSyncErrorCode {
127+
TooLarge = 'TooLarge',
127128
Unauthroized = 'Unauthroized',
128129
Rejected = 'Rejected',
129-
Unknown = 'Unknown'
130+
Unknown = 'Unknown',
131+
TooManyFailures = 'TooManyFailures',
132+
ConnectionRefused = 'ConnectionRefused'
130133
}
131134

132-
export class UserDataSyncStoreError extends Error {
135+
export class UserDataSyncError extends Error {
133136

134-
constructor(message: string, public readonly code: UserDataSyncStoreErrorCode) {
137+
constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) {
135138
super(message);
136139
}
137140

@@ -151,8 +154,8 @@ export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreServi
151154
export interface IUserDataSyncStoreService {
152155
_serviceBrand: undefined;
153156
readonly userDataSyncStore: IUserDataSyncStore | undefined;
154-
read(key: string, oldValue: IUserData | null): Promise<IUserData>;
155-
write(key: string, content: string, ref: string | null): Promise<string>;
157+
read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise<IUserData>;
158+
write(key: string, content: string, ref: string | null, source?: SyncSource): Promise<string>;
156159
clear(): Promise<void>;
157160
}
158161

@@ -217,6 +220,7 @@ export interface IUserDataSyncService extends ISynchroniser {
217220
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
218221
export interface IUserDataAutoSyncService {
219222
_serviceBrand: any;
223+
onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>;
220224
triggerAutoSync(): Promise<void>;
221225
}
222226

src/vs/platform/userDataSync/common/userDataSyncIpc.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ export class UserDataAutoSyncChannel implements IServerChannel {
8484
constructor(private readonly service: IUserDataAutoSyncService) { }
8585

8686
listen(_: unknown, event: string): Event<any> {
87+
switch (event) {
88+
case 'onError': return this.service.onError;
89+
}
8790
throw new Error(`Event not found: ${event}`);
8891
}
8992

src/vs/platform/userDataSync/common/userDataSyncStoreService.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { Disposable, } from 'vs/base/common/lifecycle';
7-
import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
7+
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync';
88
import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request';
99
import { URI } from 'vs/base/common/uri';
1010
import { joinPath } from 'vs/base/common/resources';
@@ -27,7 +27,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
2727
this.userDataSyncStore = getUserDataSyncStore(configurationService);
2828
}
2929

30-
async read(key: string, oldValue: IUserData | null): Promise<IUserData> {
30+
async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise<IUserData> {
3131
if (!this.userDataSyncStore) {
3232
throw new Error('No settings sync store url configured.');
3333
}
@@ -40,7 +40,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
4040
headers['If-None-Match'] = oldValue.ref;
4141
}
4242

43-
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
43+
const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None);
4444

4545
if (context.res.statusCode === 304) {
4646
// There is no new value. Hence return the old value.
@@ -59,7 +59,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
5959
return { ref, content };
6060
}
6161

62-
async write(key: string, data: string, ref: string | null): Promise<string> {
62+
async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise<string> {
6363
if (!this.userDataSyncStore) {
6464
throw new Error('No settings sync store url configured.');
6565
}
@@ -70,12 +70,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
7070
headers['If-Match'] = ref;
7171
}
7272

73-
const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None);
74-
75-
if (context.res.statusCode === 412) {
76-
// There is a new value. Throw Rejected Error
77-
throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected);
78-
}
73+
const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None);
7974

8075
if (!isSuccess(context)) {
8176
throw new Error('Server returned ' + context.res.statusCode);
@@ -96,30 +91,45 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
9691
const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString();
9792
const headers: IHeaders = { 'Content-Type': 'text/plain' };
9893

99-
const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None);
94+
const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None);
10095

10196
if (!isSuccess(context)) {
10297
throw new Error('Server returned ' + context.res.statusCode);
10398
}
10499
}
105100

106-
private async request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
101+
private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise<IRequestContext> {
107102
const authToken = await this.authTokenService.getToken();
108103
if (!authToken) {
109104
throw new Error('No Auth Token Available.');
110105
}
111106
options.headers = options.headers || {};
112107
options.headers['authorization'] = `Bearer ${authToken}`;
113108

114-
const context = await this.requestService.request(options, token);
109+
let context;
110+
111+
try {
112+
context = await this.requestService.request(options, token);
113+
} catch (e) {
114+
throw new UserDataSyncError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source);
115+
}
115116

116117
if (context.res.statusCode === 401) {
117118
// Throw Unauthorized Error
118-
throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized);
119+
throw new UserDataSyncError(`Request '${options.url?.toString()}' is not authorized.`, UserDataSyncErrorCode.Unauthroized, source);
119120
}
120121

121-
return context;
122+
if (context.res.statusCode === 412) {
123+
// There is a new value. Throw Rejected Error
124+
throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed with precondition. There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source);
125+
}
122126

127+
if (context.res.statusCode === 413) {
128+
// Throw Too Large Payload Error
129+
throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed because data is too large.`, UserDataSyncErrorCode.TooLarge, source);
130+
}
131+
132+
return context;
123133
}
124134

125135
}

0 commit comments

Comments
 (0)