mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421 (#7404)
* Merge from vscode e0762af258c0b20320ed03f3871a41967acc4421 * readd svgs
This commit is contained in:
@@ -4,10 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
@@ -47,7 +46,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
@@ -70,11 +69,13 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
}
|
||||
|
||||
async sync(): Promise<boolean> {
|
||||
if (!this.configurationService.getValue<boolean>('userConfiguration.syncExtensions')) {
|
||||
if (!this.configurationService.getValue<boolean>('configurationSync.enableExtensions')) {
|
||||
this.logService.trace('Extensions: Skipping synchronising extensions as it is disabled.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.status !== SyncStatus.Idle) {
|
||||
this.logService.trace('Extensions: Skipping synchronising extensions as it is running already.');
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,30 +87,30 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Failed to Synchronise extensions as there is a new remote version available. Synchronising again...');
|
||||
this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronising again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.logService.trace('Extensions: Finised synchronising extensions.');
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
return true;
|
||||
}
|
||||
|
||||
async getRemoteExtensions(): Promise<ISyncExtension[]> {
|
||||
const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null);
|
||||
return remoteData.content ? JSON.parse(remoteData.content) : [];
|
||||
}
|
||||
stop(): void { }
|
||||
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
|
||||
return this.replaceQueue.queue(async () => {
|
||||
const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null);
|
||||
const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : [];
|
||||
const removedExtensions = remoteExtensions.filter(e => areSameExtensions(e.identifier, identifier));
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('configurationSync.extensionsToIgnore') || [];
|
||||
const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier));
|
||||
if (removedExtensions.length) {
|
||||
for (const removedExtension of removedExtensions) {
|
||||
remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1);
|
||||
}
|
||||
this.logService.info(`Extensions: Removing extension '${identifier.id}' from remote.`);
|
||||
await this.writeToRemote(remoteExtensions, remoteData.ref);
|
||||
}
|
||||
});
|
||||
@@ -123,18 +124,29 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
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);
|
||||
|
||||
// update local
|
||||
await this.updateLocalExtensions(added, removed, updated);
|
||||
if (!added.length && !removed.length && !updated.length && !remote) {
|
||||
this.logService.trace('Extensions: No changes found during synchronising extensions.');
|
||||
}
|
||||
|
||||
if (added.length || removed.length || updated.length) {
|
||||
this.logService.info('Extensions: Updating local extensions...');
|
||||
await this.updateLocalExtensions(added, removed, updated);
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
// update remote
|
||||
this.logService.info('Extensions: Updating remote extensions...');
|
||||
remoteData = await this.writeToRemote(remote, remoteData.ref);
|
||||
}
|
||||
|
||||
// update last sync
|
||||
await this.updateLastSyncValue(remoteData);
|
||||
if (remoteData.content) {
|
||||
// update last sync
|
||||
this.logService.info('Extensions: Updating last synchronised extensions...');
|
||||
await this.updateLastSyncValue(remoteData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,10 +156,11 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
* - 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 } {
|
||||
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('configurationSync.extensionsToIgnore') || [];
|
||||
// First time sync
|
||||
if (!remoteExtensions) {
|
||||
return { added: [], removed: [], updated: [], remote: localExtensions };
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronising extensions for the first time.');
|
||||
return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) };
|
||||
}
|
||||
|
||||
const uuids: Map<string, string> = new Map<string, string>();
|
||||
@@ -168,8 +181,12 @@ 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 ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
|
||||
const uuid = uuids.get(id.toLowerCase());
|
||||
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
|
||||
}, new Set<string>());
|
||||
|
||||
const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap);
|
||||
const localToRemote = this.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 };
|
||||
@@ -179,8 +196,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
const removed: IExtensionIdentifier[] = [];
|
||||
const updated: ISyncExtension[] = [];
|
||||
|
||||
const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
|
||||
return {
|
||||
@@ -256,14 +273,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
}
|
||||
}
|
||||
|
||||
const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap);
|
||||
const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
|
||||
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
|
||||
return { added, removed, updated, remote };
|
||||
}
|
||||
|
||||
private compare(from: Map<string, ISyncExtension>, to: Map<string, ISyncExtension>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = keys(from);
|
||||
const toKeys = keys(to);
|
||||
private compare(from: Map<string, ISyncExtension>, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = 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>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
@@ -289,13 +306,17 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
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.extensionManagementService.uninstall(e)));
|
||||
await Promise.all(extensionsToRemove.map(e => {
|
||||
this.logService.info('Extensions: Removing local extension.', e.identifier.id);
|
||||
return this.extensionManagementService.uninstall(e);
|
||||
}));
|
||||
}
|
||||
|
||||
if (added.length || updated.length) {
|
||||
await Promise.all([...added, ...updated].map(async e => {
|
||||
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);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
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 { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancelablePromise, createCancelablePromise, ThrottledDelayer } 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 { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
@@ -48,7 +48,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
@@ -80,20 +80,28 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
}
|
||||
|
||||
async sync(_continue?: boolean): Promise<boolean> {
|
||||
if (!this.configurationService.getValue<boolean>('configurationSync.enableSettings')) {
|
||||
this.logService.trace('Settings: Skipping synchronising settings as it is disabled.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_continue) {
|
||||
this.logService.info('Settings: Resumed synchronising settings');
|
||||
return this.continueSync();
|
||||
}
|
||||
|
||||
if (this.status !== SyncStatus.Idle) {
|
||||
this.logService.trace('Settings: Skipping synchronising settings as it is running already.');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logService.trace('Settings: Started synchronising settings...');
|
||||
this.setStatus(SyncStatus.Syncing);
|
||||
|
||||
try {
|
||||
const result = await this.getPreview();
|
||||
if (result.hasConflicts) {
|
||||
this.logService.info('Settings: Detected conflicts while synchronising settings.');
|
||||
this.setStatus(SyncStatus.HasConflicts);
|
||||
return false;
|
||||
}
|
||||
@@ -104,18 +112,28 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) {
|
||||
// Rejected as there is a new remote version. Syncing again,
|
||||
this.logService.info('Failed to Synchronise settings as there is a new remote version available. Synchronising again...');
|
||||
this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronising again...');
|
||||
return this.sync();
|
||||
}
|
||||
if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) {
|
||||
// Rejected as there is a new local version. Syncing again.
|
||||
this.logService.info('Failed to Synchronise settings as there is a new local version available. Synchronising again...');
|
||||
this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronising again...');
|
||||
return this.sync();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise.cancel();
|
||||
this.syncPreviewResultPromise = null;
|
||||
this.logService.info('Settings: Stopped synchronising settings.');
|
||||
}
|
||||
this.fileService.del(this.environmentService.settingsSyncPreviewResource);
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
|
||||
private async continueSync(): Promise<boolean> {
|
||||
if (this.status !== SyncStatus.HasConflicts) {
|
||||
return false;
|
||||
@@ -133,19 +151,27 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource);
|
||||
const content = settingsPreivew.value.toString();
|
||||
if (this.hasErrors(content)) {
|
||||
return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
|
||||
const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
|
||||
this.logService.error(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise;
|
||||
if (!hasLocalChanged && !hasRemoteChanged) {
|
||||
this.logService.trace('Settings: No changes found during synchronising settings.');
|
||||
}
|
||||
if (hasLocalChanged) {
|
||||
this.logService.info('Settings: Updating local settings');
|
||||
await this.writeToLocal(content, fileContent);
|
||||
}
|
||||
if (hasRemoteChanged) {
|
||||
const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings()) : content;
|
||||
const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content)) : content;
|
||||
this.logService.info('Settings: Updating remote settings');
|
||||
const ref = await this.writeToRemote(remoteContent, remoteUserData.ref);
|
||||
remoteUserData = { ref, content };
|
||||
}
|
||||
if (hasLocalChanged) {
|
||||
await this.writeToLocal(content, fileContent);
|
||||
}
|
||||
if (remoteUserData.content) {
|
||||
this.logService.info('Settings: Updating last synchronised sttings');
|
||||
await this.updateLastSyncValue(remoteUserData);
|
||||
}
|
||||
|
||||
@@ -153,6 +179,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
await this.fileService.del(this.environmentService.settingsSyncPreviewResource);
|
||||
}
|
||||
|
||||
this.logService.trace('Settings: Finised synchronising settings.');
|
||||
this.syncPreviewResultPromise = null;
|
||||
this.setStatus(SyncStatus.Idle);
|
||||
}
|
||||
@@ -165,12 +192,12 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
|
||||
private getPreview(): Promise<ISyncPreviewResult> {
|
||||
if (!this.syncPreviewResultPromise) {
|
||||
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview());
|
||||
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token));
|
||||
}
|
||||
return this.syncPreviewResultPromise;
|
||||
}
|
||||
|
||||
private async generatePreview(): Promise<ISyncPreviewResult> {
|
||||
private async generatePreview(token: CancellationToken): Promise<ISyncPreviewResult> {
|
||||
const lastSyncData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData);
|
||||
const remoteContent: string | null = remoteUserData.content;
|
||||
@@ -179,39 +206,56 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
let hasLocalChanged: boolean = false;
|
||||
let hasRemoteChanged: boolean = false;
|
||||
let hasConflicts: boolean = false;
|
||||
let previewContent = null;
|
||||
|
||||
if (remoteContent) {
|
||||
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
|
||||
if (this.hasErrors(localContent)) {
|
||||
this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.');
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
if (!lastSyncData // First time sync
|
||||
|| lastSyncData.content !== localContent // Local has moved forwarded
|
||||
|| lastSyncData.content !== remoteContent // Remote has moved forwarded
|
||||
) {
|
||||
this.logService.trace('Settings Sync: Merging remote contents with settings file.');
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings());
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
hasLocalChanged = result.mergeContent !== localContent;
|
||||
hasRemoteChanged = result.mergeContent !== remoteContent;
|
||||
hasConflicts = result.hasConflicts;
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(result.mergeContent));
|
||||
previewContent = result.mergeContent;
|
||||
}
|
||||
}
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
// First time sync to remote
|
||||
if (fileContent) {
|
||||
this.logService.trace('Settings Sync: Remote contents does not exist. So sync with settings file.');
|
||||
// First time syncing to remote
|
||||
else if (fileContent) {
|
||||
this.logService.info('Settings: Remote settings does not exist. Synchronising settings for the first time.');
|
||||
hasRemoteChanged = true;
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(fileContent.value.toString()));
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
previewContent = fileContent.value.toString();
|
||||
}
|
||||
|
||||
if (previewContent && !token.isCancellationRequested) {
|
||||
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
|
||||
}
|
||||
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private getIgnoredSettings(): IStringDictionary<boolean> {
|
||||
return this.configurationService.getValue<IStringDictionary<boolean>>('userConfiguration.ignoreSettings');
|
||||
private getIgnoredSettings(settingsContent?: string): string[] {
|
||||
const value: string[] = (settingsContent ? parse(settingsContent)['configurationSync.settingsToIgnore'] : this.configurationService.getValue<string[]>('configurationSync.settingsToIgnore')) || [];
|
||||
const added: string[] = [], removed: string[] = [];
|
||||
for (const key of value) {
|
||||
if (startsWith(key, '-')) {
|
||||
removed.push(key.substring(1));
|
||||
} else {
|
||||
added.push(key);
|
||||
}
|
||||
}
|
||||
return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1);
|
||||
}
|
||||
|
||||
private async getLastSyncUserData(): Promise<IUserData | null> {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
|
||||
export class SettingsMergeChannel implements IServerChannel {
|
||||
|
||||
@@ -32,11 +31,11 @@ export class SettingsMergeChannelClient implements ISettingsMergeService {
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary<boolean>): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary<boolean>): Promise<string> {
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string> {
|
||||
return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,51 +7,76 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export function registerConfiguration() {
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
||||
.registerConfiguration({
|
||||
id: 'userConfiguration',
|
||||
order: 30,
|
||||
title: localize('userConfiguration', "User Configuration"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'userConfiguration.enableSync': {
|
||||
type: 'boolean',
|
||||
description: localize('userConfiguration.enableSync', "When enabled, synchronises User Configuration: Settings, Keybindings, Extensions & Snippets."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION
|
||||
},
|
||||
'userConfiguration.syncExtensions': {
|
||||
type: 'boolean',
|
||||
description: localize('userConfiguration.syncExtensions', "When enabled extensions are synchronised while synchronising user configuration."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'userConfiguration.ignoreSettings': {
|
||||
'type': 'object',
|
||||
description: localize('userConfiguration.ignoreSettings', "Configure settings to be ignored while syncing"),
|
||||
'default': {
|
||||
'userConfiguration.enableSync': true,
|
||||
'userConfiguration.syncExtensions': true,
|
||||
'userConfiguration.ignoreSettings': true
|
||||
},
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
'additionalProperties': {
|
||||
'anyOf': [
|
||||
{
|
||||
'type': 'boolean',
|
||||
'description': localize('ignoredSetting', "Id of the stting to be ignored. Set to true or false to enable or disable."),
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
export const DEFAULT_IGNORED_SETTINGS = [
|
||||
'configurationSync.enable',
|
||||
'configurationSync.enableSettings',
|
||||
'configurationSync.enableExtensions',
|
||||
];
|
||||
|
||||
export function registerConfiguration(): IDisposable {
|
||||
const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings';
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
id: 'configurationSync',
|
||||
order: 30,
|
||||
title: localize('configurationSync', "Configuration Sync"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'configurationSync.enable': {
|
||||
type: 'boolean',
|
||||
description: localize('configurationSync.enable', "When enabled, synchronises configuration that includes Settings and Extensions."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION
|
||||
},
|
||||
'configurationSync.enableSettings': {
|
||||
type: 'boolean',
|
||||
description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronising configuration."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'configurationSync.enableExtensions': {
|
||||
type: 'boolean',
|
||||
description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronising configuration."),
|
||||
default: true,
|
||||
scope: ConfigurationScope.APPLICATION,
|
||||
},
|
||||
'configurationSync.extensionsToIgnore': {
|
||||
'type': 'array',
|
||||
description: localize('configurationSync.extensionsToIgnore', "Configure extensions to be ignored while syncing."),
|
||||
'default': [],
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
uniqueItems: true
|
||||
},
|
||||
'configurationSync.settingsToIgnore': {
|
||||
'type': 'array',
|
||||
description: localize('configurationSync.settingsToIgnore', "Configure settings to be ignored while syncing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')),
|
||||
'default': [],
|
||||
'scope': ConfigurationScope.APPLICATION,
|
||||
$ref: ignoredSettingsSchemaId,
|
||||
additionalProperties: true,
|
||||
uniqueItems: true
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const registerIgnoredSettingsSchema = () => {
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
const ignoredSettingsSchema: IJSONSchema = {
|
||||
items: {
|
||||
type: 'string',
|
||||
enum: [...Object.keys(allSettings.properties).filter(setting => DEFAULT_IGNORED_SETTINGS.indexOf(setting) === -1), ...DEFAULT_IGNORED_SETTINGS.map(setting => `-${setting}`)]
|
||||
}
|
||||
};
|
||||
jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema);
|
||||
};
|
||||
return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema());
|
||||
}
|
||||
|
||||
export interface IUserData {
|
||||
@@ -113,6 +138,7 @@ export interface ISynchroniser {
|
||||
readonly onDidChangeLocal: Event<void>;
|
||||
|
||||
sync(_continue?: boolean): Promise<boolean>;
|
||||
stop(): void;
|
||||
}
|
||||
|
||||
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
|
||||
@@ -121,7 +147,6 @@ export interface IUserDataSyncService extends ISynchroniser {
|
||||
_serviceBrand: any;
|
||||
readonly conflictsSource: SyncSource | null;
|
||||
|
||||
getRemoteExtensions(): Promise<ISyncExtension[]>;
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -131,9 +156,15 @@ export interface ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary<boolean>): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>;
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>;
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary<boolean>): Promise<string>;
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string>;
|
||||
|
||||
}
|
||||
|
||||
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
|
||||
|
||||
export interface IUserDataSyncLogService extends ILogService {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
case 'sync': return this.service.sync(args[0]);
|
||||
case '_getInitialStatus': return Promise.resolve(this.service.status);
|
||||
case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource);
|
||||
case 'getRemoteExtensions': return this.service.getRemoteExtensions();
|
||||
case 'removeExtension': return this.service.removeExtension(args[0]);
|
||||
case 'stop': this.service.stop(); return Promise.resolve();
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
|
||||
47
src/vs/platform/userDataSync/common/userDataSyncLog.ts
Normal file
47
src/vs/platform/userDataSync/common/userDataSyncLog.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
export class UserDataSyncLogService extends AbstractLogService implements IUserDataSyncLogService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
private readonly logger: ILogger;
|
||||
|
||||
constructor(
|
||||
@ILoggerService loggerService: ILoggerService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
) {
|
||||
super();
|
||||
this.logger = this._register(loggerService.getLogger(environmentService.userDataSyncLogResource));
|
||||
}
|
||||
|
||||
trace(message: string, ...args: any[]): void {
|
||||
this.logger.trace(message, ...args);
|
||||
}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
this.logger.debug(message, ...args);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
this.logger.info(message, ...args);
|
||||
}
|
||||
|
||||
warn(message: string, ...args: any[]): void {
|
||||
this.logger.warn(message, ...args);
|
||||
}
|
||||
|
||||
error(message: string | Error, ...args: any[]): void {
|
||||
this.logger.error(message, ...args);
|
||||
}
|
||||
|
||||
critical(message: string | Error, ...args: any[]): void {
|
||||
this.logger.critical(message, ...args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
@@ -57,8 +57,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
|
||||
return true;
|
||||
}
|
||||
|
||||
getRemoteExtensions(): Promise<ISyncExtension[]> {
|
||||
return this.extensionsSynchroniser.getRemoteExtensions();
|
||||
stop(): void {
|
||||
if (!this.userDataSyncStoreService.enabled) {
|
||||
throw new Error('Not enabled');
|
||||
}
|
||||
for (const synchroniser of this.synchronisers) {
|
||||
synchroniser.stop();
|
||||
}
|
||||
}
|
||||
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void> {
|
||||
@@ -108,11 +113,20 @@ export class UserDataAutoSync extends Disposable {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@IUserDataSyncLogService private readonly userDataSyncLogService: IUserDataSyncLogService,
|
||||
) {
|
||||
super();
|
||||
if (userDataSyncStoreService.enabled) {
|
||||
this.sync(true);
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('userConfiguration.enableSync'))(() => this.sync(true)));
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => {
|
||||
if (this.isSyncEnabled()) {
|
||||
userDataSyncLogService.info('Syncing configuration started...');
|
||||
this.sync(true);
|
||||
} else {
|
||||
this.userDataSyncService.stop();
|
||||
userDataSyncLogService.info('Syncing configuration stopped.');
|
||||
}
|
||||
}));
|
||||
|
||||
// Sync immediately if there is a local change.
|
||||
this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false)));
|
||||
@@ -124,7 +138,7 @@ export class UserDataAutoSync extends Disposable {
|
||||
try {
|
||||
await this.userDataSyncService.sync();
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
this.userDataSyncLogService.error(e);
|
||||
}
|
||||
if (loop) {
|
||||
await timeout(1000 * 5); // Loop sync for every 5s.
|
||||
@@ -134,7 +148,7 @@ export class UserDataAutoSync extends Disposable {
|
||||
}
|
||||
|
||||
private isSyncEnabled(): boolean {
|
||||
return this.configurationService.getValue<boolean>('userConfiguration.enableSync');
|
||||
return this.configurationService.getValue<boolean>('configurationSync.enable');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user