Skip to content

Commit 74b4831

Browse files
committed
microsoft#86678 Prepare for syncing snippets
- Get conflicts resources from syncronisers - adjust conflicts handling methods to take conflicts resources - fix back up local data before accepting incoming changes
1 parent 07d9139 commit 74b4831

16 files changed

Lines changed: 252 additions & 213 deletions

File tree

src/vs/platform/environment/common/environment.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,6 @@ export interface IEnvironmentService extends IUserHomeProvider {
128128
// sync resources
129129
userDataSyncLogResource: URI;
130130
userDataSyncHome: URI;
131-
settingsSyncPreviewResource: URI;
132-
keybindingsSyncPreviewResource: URI;
133131

134132
machineSettingsResource: URI;
135133

src/vs/platform/environment/node/environmentService.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,6 @@ export class EnvironmentService implements IEnvironmentService {
114114
@memoize
115115
get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); }
116116

117-
@memoize
118-
get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); }
119-
120-
@memoize
121-
get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'keybindings.json'); }
122-
123117
@memoize
124118
get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); }
125119

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

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
77
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
88
import { VSBuffer } from 'vs/base/common/buffer';
99
import { URI } from 'vs/base/common/uri';
10-
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
10+
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict } from 'vs/platform/userDataSync/common/userDataSync';
1111
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
12-
import { joinPath, dirname } from 'vs/base/common/resources';
12+
import { joinPath, dirname, isEqual } from 'vs/base/common/resources';
1313
import { CancelablePromise } from 'vs/base/common/async';
1414
import { Emitter, Event } from 'vs/base/common/event';
1515
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -20,6 +20,7 @@ import { localize } from 'vs/nls';
2020
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2121
import { isString } from 'vs/base/common/types';
2222
import { uppercaseFirstLetter } from 'vs/base/common/strings';
23+
import { equals } from 'vs/base/common/arrays';
2324

2425
type SyncSourceClassification = {
2526
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
@@ -51,6 +52,11 @@ export abstract class AbstractSynchroniser extends Disposable {
5152
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
5253
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
5354

55+
private _conflicts: Conflict[] = [];
56+
get conflicts(): Conflict[] { return this._conflicts; }
57+
private _onDidChangeConflicts: Emitter<Conflict[]> = this._register(new Emitter<Conflict[]>());
58+
readonly onDidChangeConflicts: Event<Conflict[]> = this._onDidChangeConflicts.event;
59+
5460
protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
5561
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
5662

@@ -87,6 +93,16 @@ export abstract class AbstractSynchroniser extends Disposable {
8793
// Log to telemetry when conflicts are resolved
8894
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource });
8995
}
96+
if (this.status !== SyncStatus.HasConflicts) {
97+
this.setConflicts([]);
98+
}
99+
}
100+
}
101+
102+
protected setConflicts(conflicts: Conflict[]) {
103+
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote))) {
104+
this._conflicts = conflicts;
105+
this._onDidChangeConflicts.fire(this._conflicts);
90106
}
91107
}
92108

@@ -154,7 +170,7 @@ export abstract class AbstractSynchroniser extends Disposable {
154170
return !!lastSyncData;
155171
}
156172

157-
async getRemoteContentFromPreview(): Promise<string | null> {
173+
async getConflictContent(conflictResource: URI): Promise<string | null> {
158174
return null;
159175
}
160176

@@ -285,15 +301,22 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
285301
this.cancel();
286302
this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
287303
try {
288-
await this.fileService.del(this.conflictsPreviewResource);
304+
await this.fileService.del(this.localPreviewResource);
289305
} catch (e) { /* ignore */ }
290306
this.setStatus(SyncStatus.Idle);
291307
}
292308

293-
async getRemoteContentFromPreview(): Promise<string | null> {
294-
if (this.syncPreviewResultPromise) {
295-
const result = await this.syncPreviewResultPromise;
296-
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
309+
async getConflictContent(conflictResource: URI): Promise<string | null> {
310+
if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) {
311+
if (this.syncPreviewResultPromise) {
312+
const result = await this.syncPreviewResultPromise;
313+
if (isEqual(this.remotePreviewResource, conflictResource)) {
314+
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
315+
}
316+
if (isEqual(this.localPreviewResource, conflictResource)) {
317+
return result.fileContent ? result.fileContent.value.toString() : null;
318+
}
319+
}
297320
}
298321
return null;
299322
}
@@ -356,7 +379,8 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
356379
}
357380
}
358381

359-
protected abstract readonly conflictsPreviewResource: URI;
382+
protected abstract readonly localPreviewResource: URI;
383+
protected abstract readonly remotePreviewResource: URI;
360384
}
361385

362386
export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {

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

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm
1111
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
1212
import { IFileService } from 'vs/platform/files/common/files';
1313
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
14-
import { localize } from 'vs/nls';
1514
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
1615
import { isNonEmptyArray } from 'vs/base/common/arrays';
1716
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
1817
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
18+
import { URI } from 'vs/base/common/uri';
1919

2020
interface ISyncPreviewResult {
2121
readonly localExtensions: ISyncExtension[];
@@ -147,7 +147,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
147147
return null;
148148
}
149149

150-
accept(content: string): Promise<void> {
150+
async acceptConflict(conflict: URI, content: string): Promise<void> {
151151
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
152152
}
153153

@@ -230,9 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
230230
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
231231
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
232232
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
233-
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...', extensionToRemove.identifier.i`);
233+
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
234234
await this.extensionManagementService.uninstall(extensionToRemove);
235-
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.', extensionToRemove.identifier.i`);
235+
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
236236
removeFromSkipped.push(extensionToRemove.identifier);
237237
}));
238238
}
@@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
245245
// Builtin Extension: Sync only enablement state
246246
if (installedExtension && installedExtension.type === ExtensionType.System) {
247247
if (e.disabled) {
248-
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.i`);
248+
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
249249
await this.extensionEnablementService.disableExtension(e.identifier);
250-
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.i`);
250+
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id);
251251
} else {
252-
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.i`);
252+
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id);
253253
await this.extensionEnablementService.enableExtension(e.identifier);
254-
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.i`);
254+
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id);
255255
}
256256
removeFromSkipped.push(e.identifier);
257257
return;
@@ -261,25 +261,25 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
261261
if (extension) {
262262
try {
263263
if (e.disabled) {
264-
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.id, extension.versio`);
264+
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version);
265265
await this.extensionEnablementService.disableExtension(extension.identifier);
266-
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.id, extension.versio`);
266+
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version);
267267
} else {
268-
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.id, extension.versio`);
268+
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version);
269269
await this.extensionEnablementService.enableExtension(extension.identifier);
270-
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.id, extension.versio`);
270+
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
271271
}
272272
// Install only if the extension does not exist
273273
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
274-
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...', e.identifier.id, extension.versio`);
274+
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
275275
await this.extensionManagementService.installFromGallery(extension);
276-
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.', e.identifier.id, extension.versio`);
276+
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
277277
removeFromSkipped.push(extension.identifier);
278278
}
279279
} catch (error) {
280280
addToSkipped.push(e);
281281
this.logService.error(error);
282-
this.logService.info(localize('skip extension', "Skipped synchronizing extension {0}", extension.displayName || extension.identifier.id));
282+
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
283283
}
284284
} else {
285285
addToSkipped.push(e);

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { parse } from 'vs/base/common/json';
1616
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
1717
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1818
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
19+
import { URI } from 'vs/base/common/uri';
1920

2021
const argvProperties: string[] = ['locale'];
2122

@@ -131,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
131132
return null;
132133
}
133134

134-
accept(content: string): Promise<void> {
135+
async acceptConflict(conflict: URI, content: string): Promise<void> {
135136
throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`);
136137
}
137138

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

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

66
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
7-
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
7+
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } 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 } from 'vs/base/common/json';
@@ -19,6 +19,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
1919
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
2020
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2121
import { URI } from 'vs/base/common/uri';
22+
import { joinPath, isEqual } from 'vs/base/common/resources';
2223

2324
interface ISyncContent {
2425
mac?: string;
@@ -29,8 +30,9 @@ interface ISyncContent {
2930

3031
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
3132

32-
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
3333
protected readonly version: number = 1;
34+
protected readonly localPreviewResource: URI = joinPath(this.syncFolder, PREVIEW_DIR_NAME, 'keybindings.json');
35+
protected readonly remotePreviewResource: URI = this.localPreviewResource.with({ scheme: USER_DATA_SYNC_SCHEME });
3436

3537
constructor(
3638
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -39,7 +41,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
3941
@IConfigurationService configurationService: IConfigurationService,
4042
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
4143
@IFileService fileService: IFileService,
42-
@IEnvironmentService private readonly environmentService: IEnvironmentService,
44+
@IEnvironmentService environmentService: IEnvironmentService,
4345
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
4446
@ITelemetryService telemetryService: ITelemetryService,
4547
) {
@@ -129,8 +131,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
129131

130132
}
131133

132-
async accept(content: string): Promise<void> {
133-
if (this.status === SyncStatus.HasConflicts) {
134+
async acceptConflict(conflict: URI, content: string): Promise<void> {
135+
if (this.status === SyncStatus.HasConflicts
136+
&& (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict))
137+
) {
134138
const preview = await this.syncPreviewResultPromise!;
135139
this.cancel();
136140
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
@@ -156,8 +160,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
156160
return false;
157161
}
158162

159-
async getRemoteContentFromPreview(): Promise<string | null> {
160-
const content = await super.getRemoteContentFromPreview();
163+
async getConflictContent(conflictResource: URI): Promise<string | null> {
164+
const content = await super.getConflictContent(conflictResource);
161165
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
162166
}
163167

@@ -224,7 +228,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
224228

225229
if (hasLocalChanged) {
226230
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
227-
await this.backupLocal(this.toSyncContent(content, null));
231+
if (fileContent) {
232+
await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null));
233+
}
228234
await this.updateLocalFileContent(content, fileContent);
229235
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
230236
}
@@ -238,7 +244,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
238244

239245
// Delete the preview
240246
try {
241-
await this.fileService.del(this.conflictsPreviewResource);
247+
await this.fileService.del(this.localPreviewResource);
242248
} catch (e) { /* ignore */ }
243249
} else {
244250
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing keybindings.`);
@@ -303,9 +309,11 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
303309
}
304310

305311
if (content && !token.isCancellationRequested) {
306-
await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(content));
312+
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
307313
}
308314

315+
this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []);
316+
309317
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
310318
}
311319

0 commit comments

Comments
 (0)