提交 240d1b73 编写于 作者: S Sandeep Somavarapu

Fix #98513

上级 1fd39577
......@@ -7,6 +7,7 @@ import { values, keys } from 'vs/base/common/map';
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { startsWith } from 'vs/base/common/strings';
import { deepClone } from 'vs/base/common/objects';
export interface IMergeResult {
added: ISyncExtension[];
......@@ -30,8 +31,6 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
};
}
// massage incoming extension - add disabled property
const massageIncomingExtension = (extension: ISyncExtension): ISyncExtension => ({ ...extension, ...{ disabled: !!extension.disabled } });
localExtensions = localExtensions.map(massageIncomingExtension);
remoteExtensions = remoteExtensions.map(massageIncomingExtension);
lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null;
......@@ -54,7 +53,14 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
};
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce((map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const key = getKey(extension);
extension = deepClone(extension);
if (localExtensionsMap.get(key)?.installed) {
extension.installed = true;
}
return addExtensionToMap(map, extension);
}, new Map<string, ISyncExtension>());
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
......@@ -63,90 +69,82 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
}, new Set<string>());
const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { added: [], removed: [], updated: [], remote: null };
}
if (localToRemote.added.size > 0 || localToRemote.removed.size > 0 || localToRemote.updated.size > 0) {
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
// massage outgoing extension - remove disabled property
const massageOutgoingExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
const massagedExtension: ISyncExtension = {
identifier: {
id: extension.identifier.id,
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
},
};
if (extension.disabled) {
massagedExtension.disabled = true;
}
if (extension.version) {
massagedExtension.version = extension.version;
}
return massagedExtension;
};
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
// Remotely removed extension.
for (const key of values(baseToRemote.removed)) {
const e = localExtensionsMap.get(key);
if (e) {
removed.push(e.identifier);
// Remotely removed extension.
for (const key of values(baseToRemote.removed)) {
const e = localExtensionsMap.get(key);
if (e) {
removed.push(e.identifier);
}
}
}
// Remotely added extension
for (const key of values(baseToRemote.added)) {
// Got added in local
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
// Remotely added extension
for (const key of values(baseToRemote.added)) {
// Got added in local
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
} else {
// Add only installed extension to local
const remoteExtension = remoteExtensionsMap.get(key)!;
if (remoteExtension.installed) {
added.push(massageOutgoingExtension(remoteExtension, key));
}
}
} else {
// Add to local
added.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
}
// Remotely updated extensions
for (const key of values(baseToRemote.updated)) {
// Update in local always
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
// Locally added extensions
for (const key of values(baseToLocal.added)) {
// Not there in remote
if (!baseToRemote.added.has(key)) {
newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!);
// Remotely updated extensions
for (const key of values(baseToRemote.updated)) {
// Update in local always
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
}
// Locally updated extensions
for (const key of values(baseToLocal.updated)) {
// If removed in remote
if (baseToRemote.removed.has(key)) {
continue;
// Locally added extensions
for (const key of values(baseToLocal.added)) {
// Not there in remote
if (!baseToRemote.added.has(key)) {
newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!);
}
}
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!);
// Locally updated extensions
for (const key of values(baseToLocal.updated)) {
// If removed in remote
if (baseToRemote.removed.has(key)) {
continue;
}
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
const extension = deepClone(localExtensionsMap.get(key)!);
// Retain installed property
if (newRemoteExtensionsMap.get(key)?.installed) {
extension.installed = true;
}
newRemoteExtensionsMap.set(key, extension);
}
}
}
// Locally removed extensions
for (const key of values(baseToLocal.removed)) {
// If not skipped and not updated in remote
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.delete(key);
// Locally removed extensions
for (const key of values(baseToLocal.removed)) {
// If not skipped and not updated in remote
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
// Remove only if it is an installed extension
if (lastSyncExtensionsMap?.get(key)?.installed) {
newRemoteExtensionsMap.delete(key);
}
}
}
}
const remote: ISyncExtension[] = [];
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>(), { checkInstalledProperty: true });
if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) {
newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key)));
}
......@@ -154,7 +152,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
return { added, removed, updated, remote: remote.length ? remote : null };
}
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>, { checkInstalledProperty }: { checkInstalledProperty: boolean } = { checkInstalledProperty: false }): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = from ? keys(from).filter(key => !ignoredExtensions.has(key)) : [];
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
......@@ -170,6 +168,7 @@ function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISync
if (!toExtension
|| fromExtension.disabled !== toExtension.disabled
|| fromExtension.version !== toExtension.version
|| (checkInstalledProperty && fromExtension.installed !== toExtension.installed)
) {
updated.add(key);
}
......@@ -177,3 +176,28 @@ function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISync
return { added, removed, updated };
}
// massage incoming extension - add optional properties
function massageIncomingExtension(extension: ISyncExtension): ISyncExtension {
return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed } };
}
// massage outgoing extension - remove optional properties
function massageOutgoingExtension(extension: ISyncExtension, key: string): ISyncExtension {
const massagedExtension: ISyncExtension = {
identifier: {
id: extension.identifier.id,
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
},
};
if (extension.disabled) {
massagedExtension.disabled = true;
}
if (extension.installed) {
massagedExtension.installed = true;
}
if (extension.version) {
massagedExtension.version = extension.version;
}
return massagedExtension;
}
......@@ -41,7 +41,10 @@ interface ILastSyncUserData extends IRemoteUserData {
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/current.json` });
protected readonly version: number = 2;
/*
Version 3 - Introduce installed property to skip installing built in extensions
*/
protected readonly version: number = 3;
protected isEnabled(): boolean { return super.isEnabled() && this.extensionGalleryService.isEnabled(); }
constructor(
......@@ -85,7 +88,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (remoteUserData.syncData !== null) {
const localExtensions = await this.getLocalExtensions();
const remoteExtensions = this.parseExtensions(remoteUserData.syncData);
const remoteExtensions = await this.parseAndMigrateExtensions(remoteUserData.syncData);
const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions());
await this.apply({
added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData,
......@@ -204,7 +207,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
protected async performReplace(syncData: ISyncData, remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> {
const localExtensions = await this.getLocalExtensions();
const syncExtensions = this.parseExtensions(syncData);
const syncExtensions = await this.parseAndMigrateExtensions(syncData);
const { added, updated, removed } = merge(localExtensions, syncExtensions, localExtensions, [], this.getIgnoredExtensions());
await this.apply({
......@@ -215,8 +218,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionsSyncPreviewResult> {
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null;
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
const localExtensions = await this.getLocalExtensions();
......@@ -353,31 +356,50 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return newSkippedExtensions;
}
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
let extensions: ISyncExtension[] = JSON.parse(syncData.content);
if (syncData.version === 1) {
extensions = extensions.map(e => {
private async parseAndMigrateExtensions(syncData: ISyncData): Promise<ISyncExtension[]> {
const extensions = this.parseExtensions(syncData);
if (syncData.version === 1
|| syncData.version === 2
) {
const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System);
for (const extension of extensions) {
// #region Migration from v1 (enabled -> disabled)
if ((<any>e).enabled === false) {
e.disabled = true;
if (syncData.version === 1) {
if ((<any>extension).enabled === false) {
extension.disabled = true;
}
delete (<any>extension).enabled;
}
delete (<any>e).enabled;
// #endregion
return e;
});
// #region Migration from v2 (set installed property on extension)
if (syncData.version === 2) {
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
extension.installed = true;
}
}
// #endregion
}
}
return extensions;
}
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
return JSON.parse(syncData.content);
}
private async getLocalExtensions(): Promise<ISyncExtension[]> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const disabledExtensions = this.extensionEnablementService.getDisabledExtensions();
return installedExtensions
.map(({ identifier }) => {
.map(({ identifier, type }) => {
const syncExntesion: ISyncExtension = { identifier };
if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) {
syncExntesion.disabled = true;
}
if (type === ExtensionType.User) {
syncExntesion.installed = true;
}
return syncExntesion;
});
}
......
......@@ -239,6 +239,7 @@ export interface ISyncExtension {
identifier: IExtensionIdentifier;
version?: string;
disabled?: boolean;
installed?: boolean;
}
export interface IStorageValue {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册