diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 33901ed48ce795d6a7793764055684d25ab611f8..8f1d207371852bf141179c5d1b7fad64e4b1bfdc 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncResource, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, ISyncPreviewResult, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -16,9 +16,10 @@ 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'; -import { joinPath, dirname, basename } from 'vs/base/common/resources'; +import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources'; import { format } from 'vs/base/common/jsonFormatter'; import { applyEdits } from 'vs/base/common/jsonEdit'; +import { compare } from 'vs/base/common/strings'; interface IExtensionsSyncPreviewResult extends ISyncPreviewResult { readonly localExtensions: ISyncExtension[]; @@ -35,8 +36,10 @@ interface ILastSyncUserData extends IRemoteUserData { skippedExtensions: ISyncExtension[] | undefined; } + export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { + private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/data.json` }); protected readonly version: number = 2; protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); } @@ -132,28 +135,49 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse async stop(): Promise { } async getAssociatedResources({ uri }: ISyncResourceHandle): Promise<{ resource: URI, comparableResource?: URI }[]> { - return [{ resource: joinPath(uri, 'extensions.json') }]; + return [{ resource: joinPath(uri, 'extensions.json'), comparableResource: ExtensionsSynchroniser.EXTENSIONS_DATA_URI }]; } async resolveContent(uri: URI): Promise { + if (isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) { + const localExtensions = await this.getLocalExtensions(); + return this.format(localExtensions); + } + let content = await super.resolveContent(uri); if (content) { return content; } + content = await super.resolveContent(dirname(uri)); if (content) { const syncData = this.parseSyncData(content); if (syncData) { switch (basename(uri)) { case 'extensions.json': - const edits = format(syncData.content, undefined, {}); - return applyEdits(syncData.content, edits); + return this.format(this.parseExtensions(syncData)); } } } + return null; } + private format(extensions: ISyncExtension[]): string { + extensions.sort((e1, e2) => { + if (!e1.identifier.uuid && e2.identifier.uuid) { + return -1; + } + if (e1.identifier.uuid && !e2.identifier.uuid) { + return 1; + } + return compare(e1.identifier.id, e2.identifier.id); + }); + const content = JSON.stringify(extensions); + const edits = format(content, undefined, {}); + return applyEdits(content, edits); + } + async acceptConflict(conflict: URI, content: string): Promise { throw new Error(`${this.syncResourceLogLabel}: Conflicts should not occur`); } @@ -216,9 +240,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } if (hasLocalChanged) { - // back up all disabled or market place extensions - const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid); - await this.backupLocal(JSON.stringify(backUpExtensions)); + await this.backupLocal(JSON.stringify(localExtensions)); skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); }