Skip to content

Commit 5ca3e09

Browse files
committed
1 parent 208d556 commit 5ca3e09

12 files changed

Lines changed: 148 additions & 74 deletions

File tree

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

Lines changed: 107 additions & 8 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 { Delayer, disposableTimeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
6+
import { Delayer, disposableTimeout, CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async';
77
import { Event, Emitter } from 'vs/base/common/event';
88
import { Disposable, toDisposable, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle';
99
import { IUserDataSyncLogService, IUserDataSyncService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncResourceEnablementService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
@@ -13,6 +13,10 @@ import { isPromiseCanceledError } from 'vs/base/common/errors';
1313
import { CancellationToken } from 'vs/base/common/cancellation';
1414
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
1515
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
16+
import { IUserDataSyncMachine, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
17+
import { PlatformToString, isWeb, Platform, platform } from 'vs/base/common/platform';
18+
import { escapeRegExpCharacters } from 'vs/base/common/strings';
19+
import { IProductService } from 'vs/platform/product/common/productService';
1620

1721
type AutoSyncClassification = {
1822
sources: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -23,6 +27,7 @@ type AutoSyncEnablementClassification = {
2327
};
2428

2529
const enablementKey = 'sync.enable';
30+
const disableMachineEventuallyKey = 'sync.disableMachineEventually';
2631

2732
export class UserDataAutoSyncEnablementService extends Disposable {
2833

@@ -80,6 +85,8 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
8085
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
8186
@IUserDataSyncAccountService private readonly authTokenService: IUserDataSyncAccountService,
8287
@ITelemetryService private readonly telemetryService: ITelemetryService,
88+
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
89+
@IProductService private readonly productService: IProductService,
8390
@IStorageService storageService: IStorageService,
8491
@IEnvironmentService environmentService: IEnvironmentService
8592
) {
@@ -88,6 +95,14 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
8895

8996
if (userDataSyncStoreService.userDataSyncStore) {
9097
this.updateAutoSync();
98+
99+
// Update machine if sync is enabled
100+
if (this.isEnabled()) {
101+
this.updateMachine(true);
102+
} else if (this.hasToDisableMachineEventually()) {
103+
this.disableMachineEventually();
104+
}
105+
91106
this._register(authTokenService.onDidChangeAccount(() => this.updateAutoSync()));
92107
this._register(Event.debounce<string, string[]>(userDataSyncService.onDidChangeLocal, (last, source) => last ? [...last, source] : [source], 1000)(sources => this.triggerSync(sources, false)));
93108
this._register(Event.filter(this.userDataSyncResourceEnablementService.onDidChangeResourceEnablement, ([, enabled]) => enabled)(() => this.triggerSync(['resourceEnablement'], false)));
@@ -128,6 +143,8 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
128143
}
129144

130145
async turnOn(pullFirst: boolean): Promise<void> {
146+
await this.updateMachine(true);
147+
131148
if (pullFirst) {
132149
await this.userDataSyncService.pull();
133150
} else {
@@ -137,8 +154,27 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
137154
this.setEnablement(true);
138155
}
139156

140-
async turnOff(): Promise<void> {
141-
this.setEnablement(false);
157+
async turnOff(everywhere: boolean, softTurnOffOnError?: boolean, donotDisableMachine?: boolean): Promise<void> {
158+
try {
159+
if (!donotDisableMachine) {
160+
await this.updateMachine(false);
161+
}
162+
this.setEnablement(false);
163+
164+
if (everywhere) {
165+
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
166+
await this.userDataSyncService.reset();
167+
} else {
168+
await this.userDataSyncService.resetLocal();
169+
}
170+
} catch (error) {
171+
if (softTurnOffOnError) {
172+
this.logService.error(error);
173+
this.setEnablement(false);
174+
} else {
175+
throw error;
176+
}
177+
}
142178
}
143179

144180
private setEnablement(enabled: boolean): void {
@@ -149,6 +185,44 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
149185
}
150186
}
151187

188+
private async updateMachine(enable: boolean): Promise<void> {
189+
if (!this.authTokenService.account) {
190+
return;
191+
}
192+
193+
const machines = await this.userDataSyncMachinesService.getMachines();
194+
const currentMachine = machines.find(machine => machine.isCurrent);
195+
if (enable) {
196+
this.stopDisableMachineEventually();
197+
// Add or enable current machine
198+
if (!currentMachine) {
199+
const name = this.computeDefaultMachineName(machines);
200+
await this.userDataSyncMachinesService.addCurrentMachine(name);
201+
this.logService.debug('Auto Sync: Added current machine to sync');
202+
} else if (currentMachine.disabled) {
203+
await this.userDataSyncMachinesService.setEnablement(currentMachine.id, true);
204+
this.logService.debug('Auto Sync: Enabled current machine to sync');
205+
}
206+
} else if (currentMachine && !currentMachine.disabled) {
207+
await this.userDataSyncMachinesService.setEnablement(currentMachine.id, false);
208+
this.logService.debug('Auto Sync: Disabled current machine to sync');
209+
}
210+
}
211+
212+
private computeDefaultMachineName(machines: IUserDataSyncMachine[]): string {
213+
const namePrefix = `${this.productService.nameLong} (${PlatformToString(isWeb ? Platform.Web : platform)})`;
214+
const nameRegEx = new RegExp(`${escapeRegExpCharacters(namePrefix)}\\s#(\\d)`);
215+
216+
let nameIndex = 0;
217+
for (const machine of machines) {
218+
const matches = nameRegEx.exec(machine.name);
219+
const index = matches ? parseInt(matches[1]) : 0;
220+
nameIndex = index > nameIndex ? index : nameIndex;
221+
}
222+
223+
return `${namePrefix} #${nameIndex + 1}`;
224+
}
225+
152226
private async onDidFinishSync(error: Error | undefined): Promise<void> {
153227
if (!error) {
154228
// Sync finished without errors
@@ -159,12 +233,12 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
159233
// Error while syncing
160234
const userDataSyncError = UserDataSyncError.toUserDataSyncError(error);
161235
if (userDataSyncError.code === UserDataSyncErrorCode.TurnedOff || userDataSyncError.code === UserDataSyncErrorCode.SessionExpired) {
162-
this.logService.info('Auto Sync: Sync is turned off in the cloud.');
163-
await this.userDataSyncService.resetLocal();
164-
this.turnOff();
236+
await this.turnOff(false, true /* force soft turnoff on error */);
165237
this.logService.info('Auto Sync: Turned off sync because sync is turned off in the cloud');
166-
} else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests) {
167-
this.turnOff();
238+
} else if (userDataSyncError.code === UserDataSyncErrorCode.LocalTooManyRequests || userDataSyncError.code === UserDataSyncErrorCode.TooManyRequests) {
239+
await this.turnOff(false, true /* force soft turnoff on error */,
240+
true /* do not disable machine because disabling a machine makes request to server and can fail with TooManyRequests */);
241+
this.disableMachineEventually();
168242
this.logService.info('Auto Sync: Turned off sync because of making too many requests to server');
169243
} else {
170244
this.logService.error(userDataSyncError);
@@ -173,6 +247,31 @@ export class UserDataAutoSyncService extends UserDataAutoSyncEnablementService i
173247
this._onError.fire(userDataSyncError);
174248
}
175249

250+
private async disableMachineEventually(): Promise<void> {
251+
this.storageService.store(disableMachineEventuallyKey, true, StorageScope.GLOBAL);
252+
await timeout(1000 * 60 * 10);
253+
254+
// Return if got stopped meanwhile.
255+
if (!this.hasToDisableMachineEventually()) {
256+
return;
257+
}
258+
259+
this.stopDisableMachineEventually();
260+
261+
// disable only if sync is disabled
262+
if (!this.isEnabled()) {
263+
return this.updateMachine(false);
264+
}
265+
}
266+
267+
private hasToDisableMachineEventually(): boolean {
268+
return this.storageService.getBoolean(disableMachineEventuallyKey, StorageScope.GLOBAL, false);
269+
}
270+
271+
private stopDisableMachineEventually(): void {
272+
this.storageService.remove(disableMachineEventuallyKey, StorageScope.GLOBAL);
273+
}
274+
176275
private sources: string[] = [];
177276
async triggerSync(sources: string[], skipIfSyncedRecently: boolean): Promise<void> {
178277
if (this.autoSync.value === undefined) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ export interface IUserDataAutoSyncService {
381381
isEnabled(): boolean;
382382
canToggleEnablement(): boolean;
383383
turnOn(pullFirst: boolean): Promise<void>;
384-
turnOff(): Promise<void>;
384+
turnOff(everywhere: boolean): Promise<void>;
385385
triggerSync(sources: string[], hasToLimitSync: boolean): Promise<void>;
386386
}
387387

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class UserDataAutoSyncChannel implements IServerChannel {
7777
switch (command) {
7878
case 'triggerSync': return this.service.triggerSync(args[0], args[1]);
7979
case 'turnOn': return this.service.turnOn(args[0]);
80-
case 'turnOff': return this.service.turnOff();
80+
case 'turnOff': return this.service.turnOff(args[0]);
8181
}
8282
throw new Error('Invalid call');
8383
}

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

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ import { URI } from 'vs/base/common/uri';
1919
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
2020
import { isEqual } from 'vs/base/common/resources';
2121
import { SnippetsSynchroniser } from 'vs/platform/userDataSync/common/snippetsSync';
22-
import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines';
23-
import { IProductService } from 'vs/platform/product/common/productService';
24-
import { platform, PlatformToString, isWeb, Platform } from 'vs/base/common/platform';
25-
import { escapeRegExpCharacters } from 'vs/base/common/strings';
22+
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
2623
import { CancellationToken } from 'vs/base/common/cancellation';
2724
import { generateUuid } from 'vs/base/common/uuid';
2825
import { IHeaders } from 'vs/base/parts/request/common/request';
@@ -74,7 +71,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
7471
@ITelemetryService private readonly telemetryService: ITelemetryService,
7572
@IStorageService private readonly storageService: IStorageService,
7673
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
77-
@IProductService private readonly productService: IProductService
7874
) {
7975
super();
8076
this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser));
@@ -175,17 +171,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
175171
}
176172

177173
const machines = await this.userDataSyncMachinesService.getMachines(manifest || undefined);
178-
const currentMachine = machines.find(machine => machine.isCurrent);
179-
180174
// Return if cancellation is requested
181175
if (token.isCancellationRequested) {
182176
return;
183177
}
184178

179+
const currentMachine = machines.find(machine => machine.isCurrent);
185180
// Check if sync was turned off from other machine
186181
if (currentMachine?.disabled) {
187-
// Unset the current machine
188-
await this.userDataSyncMachinesService.removeCurrentMachine(manifest || undefined);
189182
// Throw TurnedOff error
190183
throw new UserDataSyncError(localize('turned off machine', "Cannot sync because syncing is turned off on this machine from another machine."), UserDataSyncErrorCode.TurnedOff);
191184
}
@@ -223,12 +216,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
223216
return;
224217
}
225218

226-
// Add current machine
227-
if (!currentMachine) {
228-
const name = this.computeDefaultMachineName(machines);
229-
await this.userDataSyncMachinesService.addCurrentMachine(name, manifest || undefined);
230-
}
231-
232219
// Return if cancellation is requested
233220
if (token.isCancellationRequested) {
234221
return;
@@ -346,16 +333,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
346333
async reset(): Promise<void> {
347334
await this.checkEnablement();
348335
await this.resetRemote();
349-
await this.resetLocal(true);
336+
await this.resetLocal();
350337
}
351338

352-
async resetLocal(donotUnsetMachine?: boolean): Promise<void> {
339+
async resetLocal(): Promise<void> {
353340
await this.checkEnablement();
354341
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
355342
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
356-
if (!donotUnsetMachine) {
357-
await this.userDataSyncMachinesService.removeCurrentMachine();
358-
}
359343
for (const synchroniser of this.synchronisers) {
360344
try {
361345
await synchroniser.resetLocal();
@@ -455,20 +439,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
455439
.map(s => ({ syncResource: s.resource, conflicts: s.conflicts }));
456440
}
457441

458-
private computeDefaultMachineName(machines: IUserDataSyncMachine[]): string {
459-
const namePrefix = `${this.productService.nameLong} (${PlatformToString(isWeb ? Platform.Web : platform)})`;
460-
const nameRegEx = new RegExp(`${escapeRegExpCharacters(namePrefix)}\\s#(\\d)`);
461-
462-
let nameIndex = 0;
463-
for (const machine of machines) {
464-
const matches = nameRegEx.exec(machine.name);
465-
const index = matches ? parseInt(matches[1]) : 0;
466-
nameIndex = index > nameIndex ? index : nameIndex;
467-
}
468-
469-
return `${namePrefix} #${nameIndex + 1}`;
470-
}
471-
472442
getSynchroniser(source: SyncResource): IUserDataSynchroniser {
473443
return this.synchronisers.filter(s => s.resource === source)[0];
474444
}

src/vs/platform/userDataSync/electron-browser/userDataAutoSyncService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/use
1111
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1212
import { IStorageService } from 'vs/platform/storage/common/storage';
1313
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
14+
import { IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
15+
import { IProductService } from 'vs/platform/product/common/productService';
1416

1517
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
1618

@@ -22,10 +24,12 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
2224
@IUserDataSyncLogService logService: IUserDataSyncLogService,
2325
@IUserDataSyncAccountService authTokenService: IUserDataSyncAccountService,
2426
@ITelemetryService telemetryService: ITelemetryService,
27+
@IUserDataSyncMachinesService userDataSyncMachinesService: IUserDataSyncMachinesService,
28+
@IProductService productService: IProductService,
2529
@IStorageService storageService: IStorageService,
2630
@IEnvironmentService environmentService: IEnvironmentService,
2731
) {
28-
super(userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, storageService, environmentService);
32+
super(userDataSyncStoreService, userDataSyncResourceEnablementService, userDataSyncService, logService, authTokenService, telemetryService, userDataSyncMachinesService, productService, storageService, environmentService);
2933

3034
this._register(Event.debounce<string, string[]>(Event.any<string>(
3135
Event.map(electronService.onWindowFocus, () => 'windowFocus'),

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,11 @@ suite('UserDataAutoSyncService', () => {
3636
// Trigger auto sync with settings change
3737
await testObject.triggerSync([SyncResource.Settings], false);
3838

39-
// Make sure only one request is made
40-
assert.deepEqual(target.requests, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
39+
// Filter out machine requests
40+
const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`));
41+
42+
// Make sure only one manifest request is made
43+
assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
4144
});
4245

4346
test('test auto sync with sync resource change triggers sync for every change', async () => {
@@ -57,7 +60,10 @@ suite('UserDataAutoSyncService', () => {
5760
await testObject.triggerSync([SyncResource.Settings], false);
5861
}
5962

60-
assert.deepEqual(target.requests, [
63+
// Filter out machine requests
64+
const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`));
65+
66+
assert.deepEqual(actual, [
6167
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} },
6268
{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }
6369
]);
@@ -78,8 +84,11 @@ suite('UserDataAutoSyncService', () => {
7884
// Trigger auto sync with window focus once
7985
await testObject.triggerSync(['windowFocus'], true);
8086

81-
// Make sure only one request is made
82-
assert.deepEqual(target.requests, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
87+
// Filter out machine requests
88+
const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`));
89+
90+
// Make sure only one manifest request is made
91+
assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
8392
});
8493

8594
test('test auto sync with non sync resource change does not trigger continuous syncs', async () => {
@@ -99,8 +108,11 @@ suite('UserDataAutoSyncService', () => {
99108
await testObject.triggerSync(['windowFocus'], true);
100109
}
101110

102-
// Make sure only one request is made
103-
assert.deepEqual(target.requests, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
111+
// Filter out machine requests
112+
const actual = target.requests.filter(request => !request.url.startsWith(`${target.url}/v1/resource/machines`));
113+
114+
// Make sure only one manifest request is made
115+
assert.deepEqual(actual, [{ type: 'GET', url: `${target.url}/v1/manifest`, headers: {} }]);
104116
});
105117

106118

0 commit comments

Comments
 (0)