Skip to content

Commit

Permalink
improvements to settings sync
Browse files Browse the repository at this point in the history
  • Loading branch information
sandy081 committed Sep 13, 2019
1 parent 0ebe7ec commit b59d0cf
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 155 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 58 additions & 6 deletions src/vs/workbench/contrib/userData/browser/userData.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IUserDataSyncService, SyncStatus } from 'vs/workbench/services/userData/common/userData';
import { IUserDataSyncService, SyncStatus, USER_DATA_PREVIEW_SCHEME } from 'vs/workbench/services/userData/common/userData';
import { localize } from 'vs/nls';
import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
Expand All @@ -18,9 +18,12 @@ import { FalseContext } from 'vs/platform/contextkey/common/contextkeys';
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { timeout } from 'vs/base/common/async';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { AcceptChangesController } from 'vs/workbench/contrib/userData/browser/userDataPreviewEditorContribution';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { URI } from 'vs/base/common/uri';
import { registerAndGetAmdImageURL } from 'vs/base/common/amd';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';

const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);

Expand Down Expand Up @@ -71,6 +74,8 @@ class AutoSyncUserData extends Disposable implements IWorkbenchContribution {

}

const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-light.svg`));
const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userData/browser/media/sync-push-dark.svg`));
class SyncContribution extends Disposable implements IWorkbenchContribution {

private readonly syncEnablementContext: IContextKey<string>;
Expand All @@ -82,7 +87,9 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
@IContextKeyService contextKeyService: IContextKeyService,
@IActivityService private readonly activityService: IActivityService,
@INotificationService private readonly notificationService: INotificationService,
@IConfigurationService private readonly configurationService: IConfigurationService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IEditorService private readonly editorService: IEditorService,
@ITextFileService private readonly textFileService: ITextFileService,
) {
super();
this.syncEnablementContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
Expand Down Expand Up @@ -139,6 +146,30 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
this.configurationService.updateValue('userConfiguration.autoSync', false);
return this.userDataSyncService.stopSync();
}

private async continueSync(): Promise<void> {
// Get the preview editor
const editorInput = this.editorService.editors.filter(input => {
const resource = input.getResource();
return resource && resource.scheme === USER_DATA_PREVIEW_SCHEME;
})[0];
// Save the preview
if (editorInput && editorInput.isDirty()) {
await this.textFileService.save(editorInput.getResource()!);
}
try {
// Continue Sync
await this.userDataSyncService.continueSync();
} catch (error) {
this.notificationService.error(error);
return;
}
// Close the preview editor
if (editorInput) {
editorInput.dispose();
}
}

private registerActions(): void {

const startSyncMenuItem: IMenuItem = {
Expand Down Expand Up @@ -187,6 +218,29 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem);

const continueSyncCommandId = 'workbench.userData.actions.continueSync';
CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue"),
iconLocation: {
light: SYNC_PUSH_LIGHT_ICON_URI,
dark: SYNC_PUSH_DARK_ICON_URI
}
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Scheme.isEqualTo(USER_DATA_PREVIEW_SCHEME)),
});

CommandsRegistry.registerCommand('sync.synchronising', () => { });
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
Expand All @@ -213,5 +267,3 @@ class SyncContribution extends Disposable implements IWorkbenchContribution {
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(SyncContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(AutoSyncUserData, LifecyclePhase.Eventually);

registerEditorContribution(AcceptChangesController);

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
remoteAgentService.getEnvironment().then(remoteEnv => this.remoteEnvironment = remoteEnv);
}

getEOL(resource: URI, language?: string): string {
getEOL(resource?: URI, language?: string): string {
const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource });
if (filesConfiguration && filesConfiguration.eol && filesConfiguration.eol !== 'auto') {
return filesConfiguration.eol;
Expand All @@ -38,12 +38,12 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer
return os === OperatingSystem.Linux || os === OperatingSystem.Macintosh ? '\n' : '\r\n';
}

private getOS(resource: URI): OperatingSystem {
private getOS(resource?: URI): OperatingSystem {
let os = OS;

const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (remoteAuthority) {
if (resource.scheme !== Schemas.file) {
if (resource && resource.scheme !== Schemas.file) {
const osCacheKey = `resource.authority.os.${remoteAuthority}`;
os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getNumber(osCacheKey, StorageScope.WORKSPACE, OS);
this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE);
Expand Down
65 changes: 34 additions & 31 deletions src/vs/workbench/services/userData/common/settingsSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { IRemoteUserDataService, IUserData, RemoteUserDataError, RemoteUserDataErrorCode, ISynchroniser, SyncStatus, SETTINGS_PREVIEW_RESOURCE } from 'vs/workbench/services/userData/common/userData';
import { VSBuffer } from 'vs/base/common/buffer';
import { parse, findNodeAtLocation, parseTree, ParseError } from 'vs/base/common/json';
import { URI } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
Expand All @@ -25,7 +24,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
import { Position } from 'vs/editor/common/core/position';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { isEqual } from 'vs/base/common/resources';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';

interface ISyncPreviewResult {
Expand Down Expand Up @@ -83,7 +81,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
this.setStatus(SyncStatus.HasConflicts);
return false;
}
await this.apply(SETTINGS_PREVIEW_RESOURCE);
await this.apply();
return true;
} catch (e) {
this.syncPreviewResultPromise = null;
Expand Down Expand Up @@ -129,35 +127,40 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return true;
}

async apply(previewResource: URI): Promise<boolean> {
if (!isEqual(previewResource, SETTINGS_PREVIEW_RESOURCE, false)) {
async continueSync(): Promise<boolean> {
if (this.status !== SyncStatus.HasConflicts) {
return false;
}
if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise;
let remoteUserData = result.remoteUserData;
const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
const content = settingsPreivew.value.toString();

const parseErrors: ParseError[] = [];
parse(content, parseErrors);
if (parseErrors.length > 0) {
return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please fix errors/warnings in it and try again."));
}
if (result.hasRemoteChanged) {
const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
remoteUserData = { ref, content };
}
if (result.hasLocalChanged) {
await this.writeToLocal(content, result.fileContent);
}
if (remoteUserData) {
this.updateLastSyncValue(remoteUserData);
}
await this.apply();
return true;
}

private async apply(): Promise<void> {
if (!this.syncPreviewResultPromise) {
return;
}
const result = await this.syncPreviewResultPromise;
let remoteUserData = result.remoteUserData;
const settingsPreivew = await this.fileService.readFile(SETTINGS_PREVIEW_RESOURCE);
const content = settingsPreivew.value.toString();

const parseErrors: ParseError[] = [];
parse(content, parseErrors);
if (parseErrors.length > 0) {
return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again."));
}
if (result.hasRemoteChanged) {
const ref = await this.writeToRemote(content, remoteUserData ? remoteUserData.ref : null);
remoteUserData = { ref, content };
}
if (result.hasLocalChanged) {
await this.writeToLocal(content, result.fileContent);
}
if (remoteUserData) {
this.updateLastSyncValue(remoteUserData);
}
this.syncPreviewResultPromise = null;
this.setStatus(SyncStatus.Idle);
return true;
}

private getPreview(): Promise<ISyncPreviewResult> {
Expand Down Expand Up @@ -225,8 +228,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
// Remote has moved forward
if (remoteUserData.ref !== lastSyncData.ref) {
this.logService.trace('Settings Sync: Remote contents have changed. Merge and Sync.');
hasRemoteChanged = true;
hasLocalChanged = lastSyncData.content !== localContent;
hasLocalChanged = true;
hasRemoteChanged = lastSyncData.content !== localContent;
const mergeResult = await this.mergeContents(localContent, remoteContent, lastSyncData.content);
return { settingsPreview: mergeResult.settingsPreview, hasLocalChanged, hasRemoteChanged, hasConflicts: mergeResult.hasConflicts };
}
Expand Down Expand Up @@ -278,8 +281,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
const base = lastSyncedContent ? parse(lastSyncedContent) : null;
const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc'));

const baseToLocal = base ? this.compare(base, local) : { added: new Set<string>(), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? this.compare(base, remote) : { added: new Set<string>(), removed: new Set<string>(), updated: new Set<string>() };
const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const localToRemote = this.compare(local, remote);

const conflicts: Set<string> = new Set<string>();
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/services/userData/common/userData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ export interface ISynchroniser {
readonly status: SyncStatus;
readonly onDidChangeStatus: Event<SyncStatus>;
sync(): Promise<boolean>;
continueSync(): Promise<boolean>;
stopSync(): Promise<void>;
handleConflicts(): boolean;
apply(previewResource: URI): Promise<boolean>;
}

export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
Expand Down
Loading

0 comments on commit b59d0cf

Please sign in to comment.