Skip to content

Commit 8a17a70

Browse files
committed
1 parent 6d5bed6 commit 8a17a70

5 files changed

Lines changed: 82 additions & 44 deletions

File tree

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

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { equals } from 'vs/base/common/arrays';
2727
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
2828
import { IStorageService } from 'vs/platform/storage/common/storage';
2929
import { CancellationToken } from 'vs/base/common/cancellation';
30+
import { IHeaders } from 'vs/base/parts/request/common/request';
3031

3132
type SyncSourceClassification = {
3233
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -75,6 +76,8 @@ export abstract class AbstractSynchroniser extends Disposable {
7576
protected readonly lastSyncResource: URI;
7677
protected readonly syncResourceLogLabel: string;
7778

79+
private syncHeaders: IHeaders = {};
80+
7881
constructor(
7982
readonly resource: SyncResource,
8083
@IFileService protected readonly fileService: IFileService,
@@ -150,39 +153,44 @@ export abstract class AbstractSynchroniser extends Disposable {
150153
}
151154
}
152155

153-
async sync(manifest: IUserDataManifest | null): Promise<void> {
154-
if (!this.isEnabled()) {
155-
if (this.status !== SyncStatus.Idle) {
156-
await this.stop();
156+
async sync(manifest: IUserDataManifest | null, headers: IHeaders = {}): Promise<void> {
157+
try {
158+
this.syncHeaders = { ...headers };
159+
if (!this.isEnabled()) {
160+
if (this.status !== SyncStatus.Idle) {
161+
await this.stop();
162+
}
163+
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`);
164+
return;
165+
}
166+
if (this.status === SyncStatus.HasConflicts) {
167+
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`);
168+
return;
169+
}
170+
if (this.status === SyncStatus.Syncing) {
171+
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`);
172+
return;
157173
}
158-
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is disabled.`);
159-
return;
160-
}
161-
if (this.status === SyncStatus.HasConflicts) {
162-
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as there are conflicts.`);
163-
return;
164-
}
165-
if (this.status === SyncStatus.Syncing) {
166-
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing ${this.resource.toLowerCase()} as it is running already.`);
167-
return;
168-
}
169-
170-
this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`);
171-
this.setStatus(SyncStatus.Syncing);
172174

173-
const lastSyncUserData = await this.getLastSyncUserData();
174-
const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData);
175+
this.logService.trace(`${this.syncResourceLogLabel}: Started synchronizing ${this.resource.toLowerCase()}...`);
176+
this.setStatus(SyncStatus.Syncing);
175177

176-
let status: SyncStatus = SyncStatus.Idle;
177-
try {
178-
status = await this.performSync(remoteUserData, lastSyncUserData);
179-
if (status === SyncStatus.HasConflicts) {
180-
this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`);
181-
} else if (status === SyncStatus.Idle) {
182-
this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`);
178+
const lastSyncUserData = await this.getLastSyncUserData();
179+
const remoteUserData = await this.getLatestRemoteUserData(manifest, lastSyncUserData);
180+
181+
let status: SyncStatus = SyncStatus.Idle;
182+
try {
183+
status = await this.performSync(remoteUserData, lastSyncUserData);
184+
if (status === SyncStatus.HasConflicts) {
185+
this.logService.info(`${this.syncResourceLogLabel}: Detected conflicts while synchronizing ${this.resource.toLowerCase()}.`);
186+
} else if (status === SyncStatus.Idle) {
187+
this.logService.trace(`${this.syncResourceLogLabel}: Finished synchronizing ${this.resource.toLowerCase()}.`);
188+
}
189+
} finally {
190+
this.setStatus(status);
183191
}
184192
} finally {
185-
this.setStatus(status);
193+
this.syncHeaders = {};
186194
}
187195
}
188196

@@ -446,14 +454,14 @@ export abstract class AbstractSynchroniser extends Disposable {
446454
return { ref: refOrLastSyncData, content };
447455
} else {
448456
const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null;
449-
return this.userDataSyncStoreService.read(this.resource, lastSyncUserData);
457+
return this.userDataSyncStoreService.read(this.resource, lastSyncUserData, this.syncHeaders);
450458
}
451459
}
452460

453461
protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
454462
const machineId = await this.currentMachineIdPromise;
455463
const syncData: ISyncData = { version: this.version, machineId, content };
456-
ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref);
464+
ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref, this.syncHeaders);
457465
return { ref, syncData };
458466
}
459467

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
2222
import { IProductService, ConfigurationSyncStore } from 'vs/platform/product/common/productService';
2323
import { distinct } from 'vs/base/common/arrays';
2424
import { isArray, isString, isObject } from 'vs/base/common/types';
25+
import { IHeaders } from 'vs/base/parts/request/common/request';
2526

2627
export const CONFIGURATION_SYNC_STORE_KEY = 'configurationSync.store';
2728

@@ -162,16 +163,20 @@ export type ServerResource = SyncResource | 'machines';
162163
export interface IUserDataSyncStoreService {
163164
readonly _serviceBrand: undefined;
164165
readonly userDataSyncStore: IUserDataSyncStore | undefined;
166+
165167
readonly onTokenFailed: Event<void>;
166168
readonly onTokenSucceed: Event<void>;
167169
setAuthToken(token: string, type: string): void;
168-
read(resource: ServerResource, oldValue: IUserData | null): Promise<IUserData>;
169-
write(resource: ServerResource, content: string, ref: string | null): Promise<string>;
170-
manifest(): Promise<IUserDataManifest | null>;
170+
171+
// Sync requests
172+
manifest(headers?: IHeaders): Promise<IUserDataManifest | null>;
173+
read(resource: ServerResource, oldValue: IUserData | null, headers?: IHeaders): Promise<IUserData>;
174+
write(resource: ServerResource, content: string, ref: string | null, headers?: IHeaders): Promise<string>;
171175
clear(): Promise<void>;
176+
delete(resource: ServerResource): Promise<void>;
177+
172178
getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>;
173179
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
174-
delete(resource: ServerResource): Promise<void>;
175180
}
176181

177182
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
@@ -301,7 +306,7 @@ export interface IUserDataSynchroniser {
301306

302307
pull(): Promise<void>;
303308
push(): Promise<void>;
304-
sync(manifest: IUserDataManifest | null): Promise<void>;
309+
sync(manifest: IUserDataManifest | null, headers?: IHeaders): Promise<void>;
305310
replace(uri: URI): Promise<boolean>;
306311
stop(): Promise<void>;
307312

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { IProductService } from 'vs/platform/product/common/productService';
2525
import { platform, PlatformToString, isWeb, Platform } from 'vs/base/common/platform';
2626
import { escapeRegExpCharacters } from 'vs/base/common/strings';
2727
import { CancellationToken } from 'vs/base/common/cancellation';
28+
import { generateUuid } from 'vs/base/common/uuid';
29+
import { IHeaders } from 'vs/base/parts/request/common/request';
2830

2931
type SyncClassification = {
3032
resource?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -159,7 +161,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
159161
}
160162

161163
this.telemetryService.publicLog2('sync/getmanifest');
162-
let manifest = await this.userDataSyncStoreService.manifest();
164+
const syncHeaders: IHeaders = { 'X-Execution-Id': generateUuid() };
165+
let manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
163166

164167
// Server has no data but this machine was synced before
165168
if (manifest === null && await this.hasPreviouslySynced()) {
@@ -195,7 +198,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
195198
return;
196199
}
197200
try {
198-
await synchroniser.sync(manifest);
201+
await synchroniser.sync(manifest, syncHeaders);
199202
} catch (e) {
200203
this.handleSynchronizerError(e, synchroniser.resource);
201204
this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]);
@@ -204,7 +207,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
204207

205208
// After syncing, get the manifest if it was not available before
206209
if (manifest === null) {
207-
manifest = await this.userDataSyncStoreService.manifest();
210+
manifest = await this.userDataSyncStoreService.manifest(syncHeaders);
208211
}
209212

210213
// Return if cancellation is requested

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,13 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
124124
}
125125
}
126126

127-
async read(resource: ServerResource, oldValue: IUserData | null): Promise<IUserData> {
127+
async read(resource: ServerResource, oldValue: IUserData | null, headers: IHeaders = {}): Promise<IUserData> {
128128
if (!this.userDataSyncStore) {
129129
throw new Error('No settings sync store url configured.');
130130
}
131131

132132
const url = joinPath(this.userDataSyncStore.url, 'resource', resource, 'latest').toString();
133-
const headers: IHeaders = {};
133+
headers = { ...headers };
134134
// Disable caching as they are cached by synchronisers
135135
headers['Cache-Control'] = 'no-cache';
136136
if (oldValue) {
@@ -156,13 +156,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
156156
return { ref, content };
157157
}
158158

159-
async write(resource: ServerResource, data: string, ref: string | null): Promise<string> {
159+
async write(resource: ServerResource, data: string, ref: string | null, headers: IHeaders = {}): Promise<string> {
160160
if (!this.userDataSyncStore) {
161161
throw new Error('No settings sync store url configured.');
162162
}
163163

164164
const url = joinPath(this.userDataSyncStore.url, 'resource', resource).toString();
165-
const headers: IHeaders = { 'Content-Type': 'text/plain' };
165+
headers = { ...headers };
166+
headers['Content-Type'] = 'text/plain';
166167
if (ref) {
167168
headers['If-Match'] = ref;
168169
}
@@ -180,13 +181,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
180181
return newRef;
181182
}
182183

183-
async manifest(): Promise<IUserDataManifest | null> {
184+
async manifest(headers: IHeaders = {}): Promise<IUserDataManifest | null> {
184185
if (!this.userDataSyncStore) {
185186
throw new Error('No settings sync store url configured.');
186187
}
187188

188189
const url = joinPath(this.userDataSyncStore.url, 'manifest').toString();
189-
const headers: IHeaders = { 'Content-Type': 'application/json' };
190+
headers = { ...headers };
191+
headers['Content-Type'] = 'application/json';
190192

191193
const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None);
192194
if (!isSuccess(context)) {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,4 +583,24 @@ suite('UserDataSyncService', () => {
583583
assert.deepEqual(testObject.conflicts, []);
584584
});
585585

586+
test('test sync send execution id header', async () => {
587+
// Setup the client
588+
const target = new UserDataSyncTestServer();
589+
const client = disposableStore.add(new UserDataSyncClient(target));
590+
await client.setUp();
591+
const testObject = client.instantiationService.get(IUserDataSyncService);
592+
593+
await testObject.sync();
594+
595+
for (const request of target.requestsWithAllHeaders) {
596+
const hasExecutionIdHeader = request.headers && request.headers['X-Execution-Id'] && request.headers['X-Execution-Id'].length > 0;
597+
if (request.url.startsWith(`${target.url}/v1/resource/machines`)) {
598+
assert.ok(!hasExecutionIdHeader, `Should not have execution header: ${request.url}`);
599+
} else {
600+
assert.ok(hasExecutionIdHeader, `Should have execution header: ${request.url}`);
601+
}
602+
}
603+
604+
});
605+
586606
});

0 commit comments

Comments
 (0)