Skip to content

Commit

Permalink
#86678 Prepare for syncing snippets
Browse files Browse the repository at this point in the history
- Get conflicts resources from syncronisers
- adjust conflicts handling methods to take conflicts resources
- fix back up local data before accepting incoming changes
  • Loading branch information
sandy081 committed Mar 18, 2020
1 parent 07d9139 commit 74b4831
Show file tree
Hide file tree
Showing 16 changed files with 252 additions and 213 deletions.
2 changes: 0 additions & 2 deletions src/vs/platform/environment/common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ export interface IEnvironmentService extends IUserHomeProvider {
// sync resources
userDataSyncLogResource: URI;
userDataSyncHome: URI;
settingsSyncPreviewResource: URI;
keybindingsSyncPreviewResource: URI;

machineSettingsResource: URI;

Expand Down
6 changes: 0 additions & 6 deletions src/vs/platform/environment/node/environmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,6 @@ export class EnvironmentService implements IEnvironmentService {
@memoize
get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, 'sync'); }

@memoize
get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); }

@memoize
get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'keybindings.json'); }

@memoize
get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); }

Expand Down
42 changes: 33 additions & 9 deletions src/vs/platform/userDataSync/common/abstractSynchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, IFileContent, FileChangesEvent, FileSystemProviderError, FileSystemProviderErrorCode, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { URI } from 'vs/base/common/uri';
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncResource, SyncStatus, IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, Conflict } from 'vs/platform/userDataSync/common/userDataSync';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { joinPath, dirname } from 'vs/base/common/resources';
import { joinPath, dirname, isEqual } from 'vs/base/common/resources';
import { CancelablePromise } from 'vs/base/common/async';
import { Emitter, Event } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
Expand All @@ -20,6 +20,7 @@ import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { isString } from 'vs/base/common/types';
import { uppercaseFirstLetter } from 'vs/base/common/strings';
import { equals } from 'vs/base/common/arrays';

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

private _conflicts: Conflict[] = [];
get conflicts(): Conflict[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<Conflict[]> = this._register(new Emitter<Conflict[]>());
readonly onDidChangeConflicts: Event<Conflict[]> = this._onDidChangeConflicts.event;

protected readonly _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;

Expand Down Expand Up @@ -87,6 +93,16 @@ export abstract class AbstractSynchroniser extends Disposable {
// Log to telemetry when conflicts are resolved
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.resource });
}
if (this.status !== SyncStatus.HasConflicts) {
this.setConflicts([]);
}
}
}

protected setConflicts(conflicts: Conflict[]) {
if (!equals(this._conflicts, conflicts, (a, b) => isEqual(a.local, b.local) && isEqual(a.remote, b.remote))) {
this._conflicts = conflicts;
this._onDidChangeConflicts.fire(this._conflicts);
}
}

Expand Down Expand Up @@ -154,7 +170,7 @@ export abstract class AbstractSynchroniser extends Disposable {
return !!lastSyncData;
}

async getRemoteContentFromPreview(): Promise<string | null> {
async getConflictContent(conflictResource: URI): Promise<string | null> {
return null;
}

Expand Down Expand Up @@ -285,15 +301,22 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
this.cancel();
this.logService.trace(`${this.syncResourceLogLabel}: Stopped synchronizing ${this.resource.toLowerCase()}.`);
try {
await this.fileService.del(this.conflictsPreviewResource);
await this.fileService.del(this.localPreviewResource);
} catch (e) { /* ignore */ }
this.setStatus(SyncStatus.Idle);
}

async getRemoteContentFromPreview(): Promise<string | null> {
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
async getConflictContent(conflictResource: URI): Promise<string | null> {
if (isEqual(this.remotePreviewResource, conflictResource) || isEqual(this.localPreviewResource, conflictResource)) {
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
if (isEqual(this.remotePreviewResource, conflictResource)) {
return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
}
if (isEqual(this.localPreviewResource, conflictResource)) {
return result.fileContent ? result.fileContent.value.toString() : null;
}
}
}
return null;
}
Expand Down Expand Up @@ -356,7 +379,8 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
}
}

protected abstract readonly conflictsPreviewResource: URI;
protected abstract readonly localPreviewResource: URI;
protected abstract readonly remotePreviewResource: URI;
}

export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroniser {
Expand Down
30 changes: 15 additions & 15 deletions src/vs/platform/userDataSync/common/extensionsSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/comm
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { localize } from 'vs/nls';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';

interface ISyncPreviewResult {
readonly localExtensions: ISyncExtension[];
Expand Down Expand Up @@ -147,7 +147,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return null;
}

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

Expand Down Expand Up @@ -230,9 +230,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...', extensionToRemove.identifier.i`);
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
await this.extensionManagementService.uninstall(extensionToRemove);
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.', extensionToRemove.identifier.i`);
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
removeFromSkipped.push(extensionToRemove.identifier);
}));
}
Expand All @@ -245,13 +245,13 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// Builtin Extension: Sync only enablement state
if (installedExtension && installedExtension.type === ExtensionType.System) {
if (e.disabled) {
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.i`);
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
await this.extensionEnablementService.disableExtension(e.identifier);
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.i`);
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id);
} else {
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.i`);
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id);
await this.extensionEnablementService.enableExtension(e.identifier);
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.i`);
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id);
}
removeFromSkipped.push(e.identifier);
return;
Expand All @@ -261,25 +261,25 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (extension) {
try {
if (e.disabled) {
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...', e.identifier.id, extension.versio`);
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id, extension.version);
await this.extensionEnablementService.disableExtension(extension.identifier);
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension', e.identifier.id, extension.versio`);
this.logService.info(`${this.syncResourceLogLabel}: Disabled extension`, e.identifier.id, extension.version);
} else {
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...', e.identifier.id, extension.versio`);
this.logService.trace(`${this.syncResourceLogLabel}: Enabling extension...`, e.identifier.id, extension.version);
await this.extensionEnablementService.enableExtension(extension.identifier);
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension', e.identifier.id, extension.versio`);
this.logService.info(`${this.syncResourceLogLabel}: Enabled extension`, e.identifier.id, extension.version);
}
// Install only if the extension does not exist
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...', e.identifier.id, extension.versio`);
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension);
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.', e.identifier.id, extension.versio`);
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
removeFromSkipped.push(extension.identifier);
}
} catch (error) {
addToSkipped.push(e);
this.logService.error(error);
this.logService.info(localize('skip extension', "Skipped synchronizing extension {0}", extension.displayName || extension.identifier.id));
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension`, extension.displayName || extension.identifier.id);
}
} else {
addToSkipped.push(e);
Expand Down
3 changes: 2 additions & 1 deletion src/vs/platform/userDataSync/common/globalStateSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { parse } from 'vs/base/common/json';
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';

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

Expand Down Expand Up @@ -131,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return null;
}

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

Expand Down
28 changes: 18 additions & 10 deletions src/vs/platform/userDataSync/common/keybindingsSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, USER_DATA_SYNC_SCHEME, PREVIEW_DIR_NAME } from 'vs/platform/userDataSync/common/userDataSync';
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json';
Expand All @@ -19,6 +19,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri';
import { joinPath, isEqual } from 'vs/base/common/resources';

interface ISyncContent {
mac?: string;
Expand All @@ -29,8 +30,9 @@ interface ISyncContent {

export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {

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

constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
Expand All @@ -39,7 +41,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
@IConfigurationService configurationService: IConfigurationService,
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
@IFileService fileService: IFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService,
@ITelemetryService telemetryService: ITelemetryService,
) {
Expand Down Expand Up @@ -129,8 +131,10 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem

}

async accept(content: string): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
async acceptConflict(conflict: URI, content: string): Promise<void> {
if (this.status === SyncStatus.HasConflicts
&& (isEqual(this.localPreviewResource, conflict) || isEqual(this.remotePreviewResource, conflict))
) {
const preview = await this.syncPreviewResultPromise!;
this.cancel();
this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content }));
Expand All @@ -156,8 +160,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return false;
}

async getRemoteContentFromPreview(): Promise<string | null> {
const content = await super.getRemoteContentFromPreview();
async getConflictContent(conflictResource: URI): Promise<string | null> {
const content = await super.getConflictContent(conflictResource);
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
}

Expand Down Expand Up @@ -224,7 +228,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem

if (hasLocalChanged) {
this.logService.trace(`${this.syncResourceLogLabel}: Updating local keybindings...`);
await this.backupLocal(this.toSyncContent(content, null));
if (fileContent) {
await this.backupLocal(this.toSyncContent(fileContent.value.toString(), null));
}
await this.updateLocalFileContent(content, fileContent);
this.logService.info(`${this.syncResourceLogLabel}: Updated local keybindings`);
}
Expand All @@ -238,7 +244,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem

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

if (content && !token.isCancellationRequested) {
await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(content));
await this.fileService.writeFile(this.localPreviewResource, VSBuffer.fromString(content));
}

this.setConflicts(hasConflicts && !token.isCancellationRequested ? [{ local: this.localPreviewResource, remote: this.remotePreviewResource }] : []);

return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
}

Expand Down
Loading

0 comments on commit 74b4831

Please sign in to comment.