From 4606c33f887bc47586ceb741bb7c4ae2f0a032a4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 20 Feb 2020 18:23:44 +0100 Subject: [PATCH] Fixes #91097, #90967, #89538, #89006 --- src/vs/base/common/date.ts | 4 +- .../userDataSync/common/userDataSync.ts | 3 + .../userDataSync/common/userDataSyncIpc.ts | 3 +- .../common/userDataSyncService.ts | 19 ++ .../userDataSync/browser/userDataSync.ts | 298 ++++++++++++------ .../electron-browser/userDataSyncService.ts | 17 +- 6 files changed, 235 insertions(+), 109 deletions(-) diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index acfc19d6edb..ec970c13103 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -13,7 +13,7 @@ const month = day * 30; const year = day * 365; // TODO[ECA]: Localize strings -export function fromNow(date: number | Date) { +export function fromNow(date: number | Date, appendAgoLabel?: boolean): string { if (typeof date !== 'number') { date = date.getTime(); } @@ -48,7 +48,7 @@ export function fromNow(date: number | Date) { unit = 'yr'; } - return `${value} ${unit}${value === 1 ? '' : 's'}`; + return `${value} ${unit}${value === 1 ? '' : 's'}${appendAgoLabel ? ' ago' : ''}`; } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index e5ae407af2f..b6e0b1120c7 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -283,6 +283,9 @@ export interface IUserDataSyncService { readonly onDidChangeLocal: Event; + readonly lastSyncTime: number | undefined; + readonly onDidChangeLastSyncTime: Event; + pull(): Promise; sync(): Promise; stop(): Promise; diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 66cb21f51e4..32547f516f2 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -19,13 +19,14 @@ export class UserDataSyncChannel implements IServerChannel { case 'onDidChangeStatus': return this.service.onDidChangeStatus; case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeLocal': return this.service.onDidChangeLocal; + case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime; } throw new Error(`Event not found: ${event}`); } call(context: any, command: string, args?: any): Promise { switch (command) { - case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources]); + case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources, this.service.lastSyncTime]); case 'sync': return this.service.sync(); case 'accept': return this.service.accept(args[0], args[1]); case 'pull': return this.service.pull(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 75920bcd8a8..755312ee7bd 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -21,6 +21,7 @@ type SyncErrorClassification = { }; const SESSION_ID_KEY = 'sync.sessionId'; +const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -40,6 +41,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _lastSyncTime: number | undefined = undefined; + get lastSyncTime(): number | undefined { return this._lastSyncTime; } + private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); + readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly globalStateSynchroniser: GlobalStateSynchroniser; @@ -63,6 +69,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); } + this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined); this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); } @@ -75,6 +82,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } + this.updateLastSyncTime(Date.now()); } async push(): Promise { @@ -86,6 +94,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } + this.updateLastSyncTime(Date.now()); } async sync(): Promise { @@ -131,6 +140,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); + this.updateLastSyncTime(Date.now()); } finally { this.updateStatus(); @@ -189,6 +199,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ async resetLocal(): Promise { await this.checkEnablement(); this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL); + this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL); for (const synchroniser of this.synchronisers) { try { synchroniser.resetLocal(); @@ -256,6 +267,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return SyncStatus.Idle; } + private updateLastSyncTime(lastSyncTime: number): void { + if (this._lastSyncTime !== lastSyncTime) { + this._lastSyncTime = lastSyncTime; + this.storageService.store(LAST_SYNC_TIME_KEY, lastSyncTime, StorageScope.GLOBAL); + this._onDidChangeLastSyncTime.fire(lastSyncTime); + } + } + private handleSyncError(e: Error, source: SyncSource): void { if (e instanceof UserDataSyncStoreError) { switch (e.code) { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 06d3c008fbd..21702db8313 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { timeout } from 'vs/base/common/async'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; @@ -13,7 +12,7 @@ import { isWeb } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ITextModel } from 'vs/editor/common/model'; import { AuthenticationSession } from 'vs/editor/common/modes'; @@ -21,8 +20,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; -import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuId, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -40,12 +39,13 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger'; -import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; +import { IActivityService, IBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication'; +import { fromNow } from 'vs/base/common/date'; const enum AuthStatus { Initializing = 'Initializing', @@ -76,6 +76,15 @@ type FirstTimeSyncClassification = { action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; +const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; +const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Sync: Turn off Sync") }; +const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; +const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; +const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; +const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Sync: Show Activity") }; +const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; + export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly userDataSyncStore: IUserDataSyncStore | undefined; @@ -88,6 +97,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly signInNotificationDisposable = this._register(new MutableDisposable()); private _activeAccount: AuthenticationSession | undefined; + private readonly syncStatusAction = this._register(new MutableDisposable()); + constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @@ -237,12 +248,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onDidChangeSyncStatus(status: SyncStatus) { this.syncStatusContext.set(status); - if (status === SyncStatus.Syncing) { - // Show syncing progress if takes more than 1s. - timeout(1000).then(() => this.updateBadge()); - } else { - this.updateBadge(); - } + this.updateBadge(); } private onDidChangeConflicts(conflicts: SyncSource[]) { @@ -392,10 +398,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo badge = new NumberBadge(1, () => localize('sign in to sync', "Sign in to Sync")); } else if (this.userDataSyncService.conflictsSources.length) { badge = new NumberBadge(this.userDataSyncService.conflictsSources.length, () => localize('has conflicts', "Sync: Conflicts Detected")); - } else if (this.userDataSyncService.status === SyncStatus.Syncing) { - badge = new ProgressBadge(() => localize('syncing', "Synchronizing User Configuration...")); - clazz = 'progress-badge'; - priority = 1; } if (badge) { @@ -612,15 +614,78 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private showSyncLog(): Promise { + private showSyncActivity(): Promise { return this.outputService.showChannel(Constants.userDataSyncLogChannelId); } private registerActions(): void { + this.registerTurnOnSyncAction(); + this.registerTurnOffSyncAction(); - const turnOnSyncCommandId = 'workbench.userData.actions.syncStart'; + this.registerSyncStatusAction(); + this.registerSignInAction(); + this.registerShowSettingsConflictsAction(); + this.registerShowKeybindingsConflictsAction(); + + this.registerConfigureSyncAction(); + this.registerShowActivityAction(); + this.registerShowSettingsAction(); + } + + private registerSyncStatusAction(): void { + const that = this; + this.syncStatusAction.value = registerAction2(class SyncStatusAction extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.syncStatus', + get title() { + if (that.userDataSyncService.status === SyncStatus.Syncing) { + return localize('sync is on with syncing', "Sync is on (syncing)"); + } + if (that.userDataSyncService.lastSyncTime) { + return localize('sync is on with time', "Sync is on (synced {0})", fromNow(that.userDataSyncService.lastSyncTime, true)); + } + return localize('sync is on', "Sync is on"); + }, + menu: { + id: MenuId.GlobalActivity, + group: '5_sync', + when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) + }, + }); + } + run(accessor: ServicesAccessor): any { + return new Promise((c, e) => { + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const quickPick = quickInputService.createQuickPick(); + quickPick.items = [ + { id: configureSyncCommand.id, label: configureSyncCommand.title }, + { id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }, + { id: showSyncActivityCommand.id, label: showSyncActivityCommand.title }, + { type: 'separator' }, + { id: stopSyncCommand.id, label: stopSyncCommand.title } + ]; + const disposables = new DisposableStore(); + disposables.add(quickPick.onDidAccept(() => { + if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { + commandService.executeCommand(quickPick.selectedItems[0].id); + } + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + }); + } + + private registerTurnOnSyncAction(): void { const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); - CommandsRegistry.registerCommand(turnOnSyncCommandId, async () => { + CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => { try { await this.turnOn(); } catch (e) { @@ -632,139 +697,162 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: turnOnSyncCommandId, + id: turnOnSyncCommand.id, title: localize('global activity turn on sync', "Turn on Sync...") }, when: turnOnSyncWhenContext, }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: turnOnSyncCommandId, - title: localize('turn on sync...', "Sync: Turn on Sync...") - }, + command: turnOnSyncCommand, when: turnOnSyncWhenContext, }); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '5_sync', command: { - id: turnOnSyncCommandId, + id: turnOnSyncCommand.id, title: localize('global activity turn on sync', "Turn on Sync...") }, when: turnOnSyncWhenContext, }); + } - const signInCommandId = 'workbench.userData.actions.signin'; - const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); - CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: signInCommandId, - title: localize('global activity sign in', "Sign in to Sync... (1)") - }, - when: signInWhenContext, - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: signInCommandId, - title: localize('sign in', "Sync: Sign in to sync...") - }, - when: signInWhenContext, + private registerTurnOffSyncAction(): void { + const that = this; + registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: stopSyncCommand.id, + title: stopSyncCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + async run(accessor: ServicesAccessor): Promise { + try { + await that.turnOff(); + } catch (e) { + if (!isPromiseCanceledError(e)) { + that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + } + } + accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' }); + } }); + } - const stopSyncCommandId = 'workbench.userData.actions.stopSync'; - CommandsRegistry.registerCommand(stopSyncCommandId, async () => { - try { - await this.turnOff(); - } catch (e) { - if (!isPromiseCanceledError(e)) { - this.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + private registerSignInAction(): void { + const that = this; + registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: signInCommand.id, + title: signInCommand.title, + menu: { + group: '5_sync', + id: MenuId.GlobalActivity, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)), + }, + }); + } + async run(): Promise { + try { + await that.signIn(); + } catch (e) { + that.notificationService.error(e); } } }); - MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_sync', - command: { - id: stopSyncCommandId, - title: localize('global activity stop sync', "Turn off Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts)) - }); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: stopSyncCommandId, - title: localize('stop sync', "Sync: Turn off Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }); - MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - group: '5_sync', - command: { - id: stopSyncCommandId, - title: localize('global activity stop sync', "Turn off Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }); + } - const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts'; + private registerShowSettingsConflictsAction(): void { const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); - CommandsRegistry.registerCommand(resolveSettingsConflictsCommandId, () => this.handleConflicts(SyncSource.Settings)); + CommandsRegistry.registerCommand(resolveSettingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Settings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: resolveSettingsConflictsCommandId, + id: resolveSettingsConflictsCommand.id, title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resolveSettingsConflictsCommandId, - title: localize('showConflicts', "Sync: Show Settings Conflicts"), - }, + command: resolveSettingsConflictsCommand, when: resolveSettingsConflictsWhenContext, }); + } - const resolveKeybindingsConflictsCommandId = 'workbench.userData.actions.resolveKeybindingsConflicts'; + private registerShowKeybindingsConflictsAction(): void { const resolveKeybindingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*keybindings.*/i); - CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommandId, () => this.handleConflicts(SyncSource.Keybindings)); + CommandsRegistry.registerCommand(resolveKeybindingsConflictsCommand.id, () => this.handleConflicts(SyncSource.Keybindings)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '5_sync', command: { - id: resolveKeybindingsConflictsCommandId, + id: resolveKeybindingsConflictsCommand.id, title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: resolveKeybindingsConflictsCommandId, - title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts"), - }, + command: resolveKeybindingsConflictsCommand, when: resolveKeybindingsConflictsWhenContext, }); - const configureSyncCommandId = 'workbench.userData.actions.configureSync'; - CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: configureSyncCommandId, - title: localize('configure sync', "Sync: Configure") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }); - - const showSyncLogCommandId = 'workbench.userData.actions.showSyncLog'; - CommandsRegistry.registerCommand(showSyncLogCommandId, () => this.showSyncLog()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: showSyncLogCommandId, - title: localize('show sync log', "Sync: Show Sync Log") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), - }); - } + + private registerConfigureSyncAction(): void { + const that = this; + registerAction2(class ShowSyncActivityAction extends Action2 { + constructor() { + super({ + id: configureSyncCommand.id, + title: configureSyncCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + run(): any { return that.configureSyncOptions(); } + }); + } + + private registerShowActivityAction(): void { + const that = this; + registerAction2(class ShowSyncActivityAction extends Action2 { + constructor() { + super({ + id: showSyncActivityCommand.id, + title: showSyncActivityCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + }, + }); + } + run(): any { return that.showSyncActivity(); } + }); + } + + private registerShowSettingsAction(): void { + registerAction2(class ShowSyncSettingsAction extends Action2 { + constructor() { + super({ + id: showSyncSettingsCommand.id, + title: showSyncSettingsCommand.title, + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), + }, + }); + } + run(accessor: ServicesAccessor): any { + accessor.get(IPreferencesService).openGlobalSettings(false, { query: 'sync:' }); + } + }); + } + } class UserDataRemoteContentProvider implements ITextModelContentProvider { diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index fe7a877f5ef..46537301337 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -29,6 +29,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _lastSyncTime: number | undefined = undefined; + get lastSyncTime(): number | undefined { return this._lastSyncTime; } + private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); + readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + constructor( @ISharedProcessService sharedProcessService: ISharedProcessService ) { @@ -43,10 +48,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return userDataSyncChannel.listen(event, arg); } }; - this.channel.call<[SyncStatus, SyncSource[]]>('_getInitialData').then(([status, conflicts]) => { + this.channel.call<[SyncStatus, SyncSource[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => { this.updateStatus(status); this.updateConflicts(conflicts); + if (lastSyncTime) { + this.updateLastSyncTime(lastSyncTime); + } this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); } @@ -93,6 +102,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._onDidChangeConflicts.fire(conflicts); } + private updateLastSyncTime(lastSyncTime: number): void { + if (this._lastSyncTime !== lastSyncTime) { + this._lastSyncTime = lastSyncTime; + this._onDidChangeLastSyncTime.fire(lastSyncTime); + } + } } registerSingleton(IUserDataSyncService, UserDataSyncService);