Merge from vscode a416c77e56ef0314ae00633faa04878151610de8 (#8600)

* Merge from vscode a416c77e56ef0314ae00633faa04878151610de8

* distro

* fix tests

* fix tests
This commit is contained in:
Anthony Dresser
2019-12-07 17:19:16 -08:00
committed by GitHub
parent a7ff238653
commit d614116b63
155 changed files with 1982 additions and 1599 deletions

View File

@@ -18,6 +18,7 @@ import { startsWith } from 'vs/base/common/strings';
import { IFileService } from 'vs/platform/files/common/files';
import { Queue } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { localize } from 'vs/nls';
export interface ISyncPreviewResult {
readonly added: ISyncExtension[];
@@ -26,6 +27,10 @@ export interface ISyncPreviewResult {
readonly remote: ISyncExtension[] | null;
}
interface ILastSyncUserData extends IUserData {
skippedExtensions: ISyncExtension[] | undefined;
}
export class ExtensionsSynchroniser extends Disposable implements ISynchroniser {
private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions';
@@ -122,14 +127,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
private async doSync(): Promise<void> {
const lastSyncData = await this.getLastSyncUserData();
let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData);
const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null;
let skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : [];
const lastSyncExtensions: ISyncExtension[] = lastSyncData ? JSON.parse(lastSyncData.content!) : null;
let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData);
const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null;
const localExtensions = await this.getLocalExtensions();
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions);
const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions);
if (!added.length && !removed.length && !updated.length && !remote) {
this.logService.trace('Extensions: No changes found during synchronizing extensions.');
@@ -137,7 +144,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
if (added.length || removed.length || updated.length) {
this.logService.info('Extensions: Updating local extensions...');
await this.updateLocalExtensions(added, removed, updated);
skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions);
}
if (remote) {
@@ -151,7 +158,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
) {
// update last sync
this.logService.info('Extensions: Updating last synchronised extensions...');
await this.updateLastSyncValue(remoteData);
await this.updateLastSyncValue({ ...remoteData, skippedExtensions });
}
}
@@ -161,7 +168,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
* - Overwrite local with remote changes. Removed, Added, Updated.
* - Update remote with those local extension which are newly added or updated or removed and untouched in remote.
*/
private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } {
private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } {
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
// First time sync
if (!remoteExtensions) {
@@ -187,6 +194,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, 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) => {
const uuid = uuids.get(id.toLowerCase());
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
@@ -273,8 +281,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
// Locally removed extensions
for (const key of values(baseToLocal.removed)) {
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
// If not skipped and not updated in remote
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.delete(key);
}
}
@@ -308,13 +316,17 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
return { added, removed, updated };
}
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[]): Promise<void> {
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
const removeFromSkipped: IExtensionIdentifier[] = [];
const addToSkipped: ISyncExtension[] = [];
if (removed.length) {
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r)));
await Promise.all(extensionsToRemove.map(e => {
this.logService.info('Extensions: Removing local extension.', e.identifier.id);
return this.extensionManagementService.uninstall(e);
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
this.logService.info('Extensions: Removing local extension.', extensionToRemove.identifier.id);
await this.extensionManagementService.uninstall(extensionToRemove);
removeFromSkipped.push(extensionToRemove.identifier);
}));
}
@@ -323,18 +335,41 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version);
if (extension) {
this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension);
try {
await this.extensionManagementService.installFromGallery(extension);
removeFromSkipped.push(extension.identifier);
} catch (error) {
addToSkipped.push(e);
this.logService.error(error);
this.logService.info(localize('skip extension', "Skipping synchronising extension {0}", extension.displayName || extension.identifier.id));
}
} else {
addToSkipped.push(e);
}
}));
}
const newSkippedExtensions: ISyncExtension[] = [];
for (const skippedExtension of skippedExtensions) {
if (!removeFromSkipped.some(e => areSameExtensions(e, skippedExtension.identifier))) {
newSkippedExtensions.push(skippedExtension);
}
}
for (const skippedExtension of addToSkipped) {
if (!newSkippedExtensions.some(e => areSameExtensions(e.identifier, skippedExtension.identifier))) {
newSkippedExtensions.push(skippedExtension);
}
}
return newSkippedExtensions;
}
private async getLocalExtensions(): Promise<ISyncExtension[]> {
const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User);
return installedExtensions.map(({ identifier }) => ({ identifier, enabled: true }));
return installedExtensions
.map(({ identifier }) => ({ identifier, enabled: true }));
}
private async getLastSyncUserData(): Promise<IUserData | null> {
private async getLastSyncUserData(): Promise<ILastSyncUserData | null> {
try {
const content = await this.fileService.readFile(this.lastSyncExtensionsResource);
return JSON.parse(content.value.toString());
@@ -343,14 +378,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
}
}
private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise<void> {
await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
}
private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise<IUserData> {
const content = JSON.stringify(extensions);
ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref);
return { content, ref };
}
private async updateLastSyncValue(remoteUserData: IUserData): Promise<void> {
await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(remoteUserData)));
}
}

View File

@@ -11,10 +11,10 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { parse, ParseError } from 'vs/base/common/json';
import { localize } from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { joinPath, dirname } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { OS, OperatingSystem } from 'vs/base/common/platform';
@@ -46,7 +46,6 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
private readonly throttledDelayer: ThrottledDelayer<void>;
private _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
@@ -62,24 +61,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
) {
super();
this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json');
this.throttledDelayer = this._register(new ThrottledDelayer<void>(500));
this._register(this.fileService.watch(this.environmentService.keybindingsResource));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings())));
}
private async onDidChangeKeybindings(): Promise<void> {
const localFileContent = await this.getLocalContent();
const lastSyncData = await this.getLastSyncUserData();
if (localFileContent && lastSyncData) {
if (localFileContent.value.toString() !== lastSyncData.content) {
this._onDidChangeLocal.fire();
return;
}
}
if (!localFileContent || !lastSyncData) {
this._onDidChangeLocal.fire();
return;
}
this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource)));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire()));
}
private setStatus(status: SyncStatus): void {

View File

@@ -10,10 +10,10 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { parse, ParseError } from 'vs/base/common/json';
import { localize } from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { joinPath, dirname } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { startsWith } from 'vs/base/common/strings';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -37,7 +37,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
private _onDidChangStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
private readonly throttledDelayer: ThrottledDelayer<void>;
private _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeLocal: Event<void> = this._onDidChangeLocal.event;
@@ -53,23 +52,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
) {
super();
this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json');
this.throttledDelayer = this._register(new ThrottledDelayer<void>(500));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings())));
}
private async onDidChangeSettings(): Promise<void> {
const localFileContent = await this.getLocalFileContent();
const lastSyncData = await this.getLastSyncUserData();
if (localFileContent && lastSyncData) {
if (localFileContent.value.toString() !== lastSyncData.content) {
this._onDidChangeLocal.fire();
return;
}
}
if (!localFileContent || !lastSyncData) {
this._onDidChangeLocal.fire();
return;
}
this._register(this.fileService.watch(dirname(this.environmentService.settingsResource)));
this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire()));
}
private setStatus(status: SyncStatus): void {