mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 10:58:31 -05:00
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -11,6 +11,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
|
||||
|
||||
@@ -22,8 +24,10 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IHostService hostService: IHostService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService);
|
||||
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService, telemetryService, productService, configurationService);
|
||||
|
||||
this._register(Event.debounce<string, string[]>(Event.any<string>(
|
||||
Event.map(hostService.onDidChangeFocus, () => 'windowFocus'),
|
||||
|
||||
@@ -3,12 +3,41 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { UserDataSyncWorkbenchContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
|
||||
import { UserDataSyncViewContribution } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView';
|
||||
import { IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
|
||||
class UserDataSyncReportIssueContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
) {
|
||||
super();
|
||||
this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
|
||||
}
|
||||
|
||||
private onAutoSyncError(error: UserDataSyncError): void {
|
||||
switch (error.code) {
|
||||
case UserDataSyncErrorCode.LocalTooManyRequests:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too many requests', "Turned off syncing preferences on this device because it is making too many requests."),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(UserDataSyncWorkbenchContribution, LifecyclePhase.Ready);
|
||||
workbenchRegistry.registerWorkbenchContribution(UserDataSyncViewContribution, LifecyclePhase.Ready);
|
||||
|
||||
if (isWeb) {
|
||||
workbenchRegistry.registerWorkbenchContribution(UserDataSyncReportIssueContribution, LifecyclePhase.Ready);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, DisposableStore, dispose, MutableDisposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isEqual, basename } from 'vs/base/common/resources';
|
||||
@@ -28,8 +28,8 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import {
|
||||
CONTEXT_SYNC_STATE, IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration,
|
||||
SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, CONTEXT_SYNC_ENABLEMENT,
|
||||
IUserDataAutoSyncService, IUserDataSyncService, registerConfiguration,
|
||||
SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService,
|
||||
SyncResourceConflicts, Conflict, getSyncResourceFromLocalPreview
|
||||
} from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
@@ -48,40 +48,30 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { UserDataSyncAccounts, AccountStatus } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncAccount';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { ViewContainerLocation, IViewContainersRegistry, Extensions, ViewContainer } from 'vs/workbench/common/views';
|
||||
import { UserDataSyncViewPaneContainer, UserDataSyncDataViews } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncViews';
|
||||
import { IUserDataSyncWorkbenchService, CONTEXT_ENABLE_VIEWS, getSyncAreaLabel, AccountStatus, CONTEXT_SYNC_STATE, CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
|
||||
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
|
||||
|
||||
type ConfigureSyncQuickPickItem = { id: SyncResource, label: string, description?: string };
|
||||
|
||||
function getSyncAreaLabel(source: SyncResource): string {
|
||||
switch (source) {
|
||||
case SyncResource.Settings: return localize('settings', "Settings");
|
||||
case SyncResource.Keybindings: return localize('keybindings', "Keyboard Shortcuts");
|
||||
case SyncResource.Snippets: return localize('snippets', "User Snippets");
|
||||
case SyncResource.Extensions: return localize('extensions', "Extensions");
|
||||
case SyncResource.GlobalState: return localize('ui state label', "UI State");
|
||||
}
|
||||
}
|
||||
|
||||
type SyncConflictsClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
action?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
type FirstTimeSyncClassification = {
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Preferences Sync: Turn On...") };
|
||||
const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Preferences Sync: Turn Off") };
|
||||
const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") };
|
||||
const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") };
|
||||
const resolveSnippetsConflictsCommand = { id: 'workbench.userData.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") };
|
||||
const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Preferences Sync: Configure...") };
|
||||
const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Preferences Sync: Show Log") };
|
||||
const turnOnSyncCommand = { id: 'workbench.userDataSync.actions.turnOn', title: localize('turn on sync with category', "Preferences Sync: Turn On...") };
|
||||
const turnOffSyncCommand = { id: 'workbench.userDataSync.actions.turnOff', title: localize('stop sync', "Preferences Sync: Turn Off") };
|
||||
const configureSyncCommand = { id: CONFIGURE_SYNC_COMMAND_ID, title: localize('configure sync', "Preferences Sync: Configure...") };
|
||||
const resolveSettingsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveSettingsConflicts', title: localize('showConflicts', "Preferences Sync: Show Settings Conflicts") };
|
||||
const resolveKeybindingsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Preferences Sync: Show Keybindings Conflicts") };
|
||||
const resolveSnippetsConflictsCommand = { id: 'workbench.userDataSync.actions.resolveSnippetsConflicts', title: localize('showSnippetsConflicts', "Preferences Sync: Show User Snippets Conflicts") };
|
||||
const syncNowCommand = {
|
||||
id: 'workbench.userData.actions.syncNow',
|
||||
id: 'workbench.userDataSync.actions.syncNow',
|
||||
title: localize('sync now', "Preferences Sync: Sync Now"),
|
||||
description(userDataSyncService: IUserDataSyncService): string | undefined {
|
||||
if (userDataSyncService.status === SyncStatus.Syncing) {
|
||||
@@ -93,25 +83,22 @@ const syncNowCommand = {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Preferences Sync: Show Settings"), };
|
||||
const showSyncSettingsCommand = { id: 'workbench.userDataSync.actions.settings', title: localize('sync settings', "Preferences Sync: Show Settings"), };
|
||||
|
||||
const CONTEXT_TURNING_ON_STATE = new RawContextKey<false>('userDataSyncTurningOn', false);
|
||||
export const CONTEXT_ACCOUNT_STATE = new RawContextKey<string>('userDataSyncAccountStatus', AccountStatus.Uninitialized);
|
||||
|
||||
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private readonly turningOnSyncContext: IContextKey<boolean>;
|
||||
private readonly syncEnablementContext: IContextKey<boolean>;
|
||||
private readonly syncStatusContext: IContextKey<string>;
|
||||
private readonly accountStatusContext: IContextKey<string>;
|
||||
private readonly conflictsSources: IContextKey<string>;
|
||||
private readonly viewsEnablementContext: IContextKey<boolean>;
|
||||
|
||||
private readonly userDataSyncAccounts: UserDataSyncAccounts;
|
||||
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@@ -119,7 +106,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IOutputService private readonly outputService: IOutputService,
|
||||
@IAuthenticationTokenService readonly authTokenService: IAuthenticationTokenService,
|
||||
@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@@ -134,45 +121,32 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
super();
|
||||
|
||||
this.turningOnSyncContext = CONTEXT_TURNING_ON_STATE.bindTo(contextKeyService);
|
||||
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
|
||||
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
|
||||
this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService);
|
||||
this.conflictsSources = CONTEXT_CONFLICTS_SOURCES.bindTo(contextKeyService);
|
||||
this.viewsEnablementContext = CONTEXT_ENABLE_VIEWS.bindTo(contextKeyService);
|
||||
|
||||
this.userDataSyncAccounts = instantiationService.createInstance(UserDataSyncAccounts);
|
||||
|
||||
if (this.userDataSyncAccounts.authenticationProviders.length) {
|
||||
if (this.userDataSyncWorkbenchService.authenticationProviders.length) {
|
||||
registerConfiguration();
|
||||
|
||||
this.onDidChangeSyncStatus(this.userDataSyncService.status);
|
||||
this.updateBadge();
|
||||
this.onDidChangeConflicts(this.userDataSyncService.conflicts);
|
||||
this.onDidChangeEnablement(this.userDataSyncEnablementService.isEnabled());
|
||||
this.onDidChangeAccountStatus(this.userDataSyncAccounts.status);
|
||||
|
||||
this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status)));
|
||||
this._register(Event.any(
|
||||
Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500),
|
||||
this.userDataSyncEnablementService.onDidChangeEnablement,
|
||||
this.userDataSyncWorkbenchService.onDidChangeAccountStatus
|
||||
)(() => this.updateBadge()));
|
||||
this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflicts)));
|
||||
this._register(userDataSyncService.onSyncErrors(errors => this.onSyncErrors(errors)));
|
||||
this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled)));
|
||||
this._register(userDataSyncService.onSyncErrors(errors => this.onSynchronizerErrors(errors)));
|
||||
this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
|
||||
this._register(this.userDataSyncAccounts.onDidChangeStatus(status => this.onDidChangeAccountStatus(status)));
|
||||
this._register(this.userDataSyncAccounts.onDidSignOut(() => this.doTurnOff(false)));
|
||||
|
||||
this.registerActions();
|
||||
this.registerViews();
|
||||
|
||||
textModelResolverService.registerTextModelContentProvider(USER_DATA_SYNC_SCHEME, instantiationService.createInstance(UserDataRemoteContentProvider));
|
||||
registerEditorContribution(AcceptChangesContribution.ID, AcceptChangesContribution);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeAccountStatus(status: AccountStatus): void {
|
||||
this.accountStatusContext.set(status);
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
private onDidChangeSyncStatus(status: SyncStatus) {
|
||||
this.syncStatusContext.set(status);
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
private readonly conflictsDisposables = new Map<SyncResource, IDisposable>();
|
||||
private onDidChangeConflicts(conflicts: SyncResourceConflicts[]) {
|
||||
this.updateBadge();
|
||||
@@ -261,8 +235,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.remote);
|
||||
await this.userDataSyncService.acceptConflict(conflict.remote, modelRef.object.textEditorModel.getValue());
|
||||
modelRef.dispose();
|
||||
try {
|
||||
await this.userDataSyncService.acceptConflict(conflict.remote, modelRef.object.textEditorModel.getValue());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
@@ -273,20 +250,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
try {
|
||||
for (const conflict of conflicts) {
|
||||
const modelRef = await this.textModelResolverService.createModelReference(conflict.local);
|
||||
await this.userDataSyncService.acceptConflict(conflict.local, modelRef.object.textEditorModel.getValue());
|
||||
modelRef.dispose();
|
||||
try {
|
||||
await this.userDataSyncService.acceptConflict(conflict.local, modelRef.object.textEditorModel.getValue());
|
||||
} finally {
|
||||
modelRef.dispose();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeEnablement(enabled: boolean) {
|
||||
this.syncEnablementContext.set(enabled);
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
private onAutoSyncError(error: UserDataSyncError): void {
|
||||
private onAutoSyncError(error: UserDataSyncError): boolean {
|
||||
switch (error.code) {
|
||||
case UserDataSyncErrorCode.TurnedOff:
|
||||
case UserDataSyncErrorCode.SessionExpired:
|
||||
@@ -297,7 +272,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
primary: [new Action('turn on sync', localize('turn on sync', "Turn on Preferences Sync..."), undefined, true, () => this.turnOn())]
|
||||
}
|
||||
});
|
||||
return;
|
||||
return true;
|
||||
case UserDataSyncErrorCode.TooLarge:
|
||||
if (error.resource === SyncResource.Keybindings || error.resource === SyncResource.Settings) {
|
||||
this.disableSync(error.resource);
|
||||
@@ -311,19 +286,21 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
return true;
|
||||
case UserDataSyncErrorCode.Incompatible:
|
||||
case UserDataSyncErrorCode.UpgradeRequired:
|
||||
this.disableSync();
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('error incompatible', "Turned off sync because local data is incompatible with the data in the cloud. Please update {0} and turn on sync to continue syncing.", this.productService.nameLong),
|
||||
message: localize('error upgrade required', "Turned off sync because the current version ({0}, {1}) of {2} is not compatible with the Preferences Sync Service. Please update and turn on sync to continue syncing.", this.productService.version, this.productService.commit, this.productService.nameLong),
|
||||
});
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly invalidContentErrorDisposables = new Map<SyncResource, IDisposable>();
|
||||
private onSyncErrors(errors: [SyncResource, UserDataSyncError][]): void {
|
||||
private onSynchronizerErrors(errors: [SyncResource, UserDataSyncError][]): void {
|
||||
if (errors.length) {
|
||||
for (const [source, error] of errors) {
|
||||
switch (error.code) {
|
||||
@@ -379,7 +356,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
let clazz: string | undefined;
|
||||
let priority: number | undefined = undefined;
|
||||
|
||||
if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncAccounts.status === AccountStatus.Unavailable) {
|
||||
if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncWorkbenchService.accountStatus === AccountStatus.Unavailable) {
|
||||
badge = new NumberBadge(1, () => localize('sign in to sync preferences', "Sign in to Sync Preferences"));
|
||||
} else if (this.userDataSyncService.conflicts.length) {
|
||||
badge = new NumberBadge(this.userDataSyncService.conflicts.reduce((result, syncResourceConflict) => { return result + syncResourceConflict.conflicts.length; }, 0), () => localize('has conflicts', "Preferences Sync: Conflicts Detected"));
|
||||
@@ -415,8 +392,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
if (!turnOn) {
|
||||
return;
|
||||
}
|
||||
await this.doTurnOn();
|
||||
await this.userDataSyncWorkbenchService.turnOn();
|
||||
this.storageService.store('sync.donotAskPreviewConfirmation', true, StorageScope.GLOBAL);
|
||||
} catch (e) {
|
||||
if (isPromiseCanceledError(e)) {
|
||||
return;
|
||||
}
|
||||
if (e instanceof UserDataSyncError && this.onAutoSyncError(e)) {
|
||||
return;
|
||||
}
|
||||
this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e)));
|
||||
} finally {
|
||||
this.turningOnSync = false;
|
||||
}
|
||||
@@ -450,13 +435,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
quickPick.title = localize('Preferences Sync Title', "Preferences Sync");
|
||||
quickPick.ok = false;
|
||||
quickPick.customButton = true;
|
||||
if (this.userDataSyncAccounts.all.length) {
|
||||
if (this.userDataSyncWorkbenchService.all.length) {
|
||||
quickPick.customLabel = localize('turn on', "Turn On");
|
||||
} else {
|
||||
const orTerm = localize({ key: 'or', comment: ['Here is the context where it is used - Sign in with your A or B or C account to synchronize your data across devices.'] }, "or");
|
||||
const displayName = this.userDataSyncAccounts.authenticationProviders.length === 1
|
||||
? this.authenticationService.getDisplayName(this.userDataSyncAccounts.authenticationProviders[0].id)
|
||||
: this.userDataSyncAccounts.authenticationProviders.map(({ id }) => this.authenticationService.getDisplayName(id)).join(` ${orTerm} `);
|
||||
const displayName = this.userDataSyncWorkbenchService.authenticationProviders.length === 1
|
||||
? this.authenticationService.getDisplayName(this.userDataSyncWorkbenchService.authenticationProviders[0].id)
|
||||
: this.userDataSyncWorkbenchService.authenticationProviders.map(({ id }) => this.authenticationService.getDisplayName(id)).join(` ${orTerm} `);
|
||||
quickPick.description = localize('sign in and turn on sync detail', "Sign in with your {0} account to synchronize your data across devices.", displayName);
|
||||
quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on");
|
||||
}
|
||||
@@ -487,22 +472,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
|
||||
private async doTurnOn(): Promise<void> {
|
||||
const picked = await this.userDataSyncAccounts.pick();
|
||||
if (!picked) {
|
||||
throw canceled();
|
||||
}
|
||||
|
||||
// User did not pick an account or login failed
|
||||
if (this.userDataSyncAccounts.status !== AccountStatus.Available) {
|
||||
throw new Error(localize('no account', "No account available"));
|
||||
}
|
||||
|
||||
await this.handleFirstTimeSync();
|
||||
this.userDataSyncEnablementService.setEnablement(true);
|
||||
this.notificationService.info(localize('sync turned on', "Preferences sync is turned on"));
|
||||
}
|
||||
|
||||
private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] {
|
||||
return [{
|
||||
id: SyncResource.Settings,
|
||||
@@ -559,38 +528,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
});
|
||||
}
|
||||
|
||||
private async handleFirstTimeSync(): Promise<void> {
|
||||
const isFirstSyncWithMerge = await this.userDataSyncService.isFirstTimeSyncWithMerge();
|
||||
if (!isFirstSyncWithMerge) {
|
||||
return;
|
||||
}
|
||||
const result = await this.dialogService.show(
|
||||
Severity.Info,
|
||||
localize('firs time sync', "Sync"),
|
||||
[
|
||||
localize('merge', "Merge"),
|
||||
localize('cancel', "Cancel"),
|
||||
localize('replace', "Replace Local"),
|
||||
],
|
||||
{
|
||||
cancelId: 1,
|
||||
detail: localize('first time sync detail', "It looks like this is the first time sync is set up.\nWould you like to merge or replace with the data from the cloud?"),
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0:
|
||||
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' });
|
||||
break;
|
||||
case 1:
|
||||
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
|
||||
throw canceled();
|
||||
case 2:
|
||||
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' });
|
||||
await this.userDataSyncService.pull();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async turnOff(): Promise<void> {
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
@@ -602,26 +539,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
});
|
||||
if (result.confirmed) {
|
||||
return this.doTurnOff(!!result.checkboxChecked);
|
||||
return this.userDataSyncWorkbenchService.turnoff(!!result.checkboxChecked);
|
||||
}
|
||||
}
|
||||
|
||||
private async doTurnOff(turnOffEveryWhere: boolean): Promise<void> {
|
||||
if (!this.userDataSyncEnablementService.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!this.userDataSyncEnablementService.canToggleEnablement()) {
|
||||
return;
|
||||
}
|
||||
if (turnOffEveryWhere) {
|
||||
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
|
||||
await this.userDataSyncService.reset();
|
||||
} else {
|
||||
await this.userDataSyncService.resetLocal();
|
||||
}
|
||||
this.disableSync();
|
||||
}
|
||||
|
||||
private disableSync(source?: SyncResource): void {
|
||||
if (source === undefined) {
|
||||
this.userDataSyncEnablementService.setEnablement(false);
|
||||
@@ -694,24 +615,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this.registerShowSettingsConflictsAction();
|
||||
this.registerShowKeybindingsConflictsAction();
|
||||
this.registerShowSnippetsConflictsAction();
|
||||
this.registerSyncStatusAction();
|
||||
|
||||
this.registerEnableSyncViewsAction();
|
||||
this.registerManageSyncAction();
|
||||
this.registerSyncNowAction();
|
||||
this.registerConfigureSyncAction();
|
||||
this.registerShowActivityAction();
|
||||
this.registerShowSettingsAction();
|
||||
this.registerShowLogAction();
|
||||
}
|
||||
|
||||
private registerTurnOnSyncAction(): void {
|
||||
const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_ACCOUNT_STATE.notEqualsTo(AccountStatus.Uninitialized), CONTEXT_TURNING_ON_STATE.negate());
|
||||
CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => {
|
||||
try {
|
||||
await this.turnOn();
|
||||
} catch (e) {
|
||||
if (!isPromiseCanceledError(e)) {
|
||||
this.notificationService.error(localize('turn on failed', "Error while starting Sync: {0}", toErrorMessage(e)));
|
||||
}
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommand(turnOnSyncCommand.id, () => this.turnOn());
|
||||
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
|
||||
group: '5_sync',
|
||||
command: {
|
||||
@@ -786,7 +701,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
async run(): Promise<any> {
|
||||
try {
|
||||
await that.userDataSyncAccounts.pick();
|
||||
await that.userDataSyncWorkbenchService.pickAccount();
|
||||
} catch (e) {
|
||||
that.notificationService.error(e);
|
||||
}
|
||||
@@ -886,14 +801,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}));
|
||||
}
|
||||
|
||||
private registerSyncStatusAction(): void {
|
||||
private registerManageSyncAction(): void {
|
||||
const that = this;
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
|
||||
this.registerSyncNowAction();
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.userData.actions.syncStatus',
|
||||
id: 'workbench.userDataSync.actions.manage',
|
||||
title: localize('sync is on', "Preferences Sync is On"),
|
||||
menu: [
|
||||
{
|
||||
@@ -941,13 +855,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title });
|
||||
items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title });
|
||||
items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title });
|
||||
items.push({ type: 'separator' });
|
||||
items.push({ id: syncNowCommand.id, label: syncNowCommand.title, description: syncNowCommand.description(that.userDataSyncService) });
|
||||
if (that.userDataSyncEnablementService.canToggleEnablement()) {
|
||||
const account = that.userDataSyncAccounts.current;
|
||||
items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined });
|
||||
const account = that.userDataSyncWorkbenchService.current;
|
||||
items.push({ id: turnOffSyncCommand.id, label: turnOffSyncCommand.title, description: account ? `${account.accountName} (${that.authenticationService.getDisplayName(account.authenticationProviderId)})` : undefined });
|
||||
}
|
||||
quickPick.items = items;
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
@@ -966,6 +878,23 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}));
|
||||
}
|
||||
|
||||
private registerEnableSyncViewsAction(): void {
|
||||
const that = this;
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized));
|
||||
this._register(registerAction2(class SyncStatusAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: ENABLE_SYNC_VIEWS_COMMAND_ID,
|
||||
title: ENABLE_SYNC_VIEWS_COMMAND_ID,
|
||||
precondition: when
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): any {
|
||||
that.viewsEnablementContext.set(true);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private registerSyncNowAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class SyncNowAction extends Action2 {
|
||||
@@ -973,9 +902,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
super({
|
||||
id: syncNowCommand.id,
|
||||
title: syncNowCommand.title,
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized))
|
||||
}
|
||||
});
|
||||
}
|
||||
run(): Promise<any> {
|
||||
run(accessor: ServicesAccessor): Promise<any> {
|
||||
accessor.get(ITelemetryService).publicLog2(`sync/actions/${syncNowCommand.id}`);
|
||||
return that.userDataSyncService.sync();
|
||||
}
|
||||
}));
|
||||
@@ -986,8 +920,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this._register(registerAction2(class StopSyncAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: stopSyncCommand.id,
|
||||
title: stopSyncCommand.title,
|
||||
id: turnOffSyncCommand.id,
|
||||
title: turnOffSyncCommand.title,
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
|
||||
@@ -1008,28 +942,29 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private registerConfigureSyncAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
const when = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT);
|
||||
this._register(registerAction2(class ConfigureSyncAction 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),
|
||||
},
|
||||
when
|
||||
}
|
||||
});
|
||||
}
|
||||
run(): any { return that.configureSyncOptions(); }
|
||||
}));
|
||||
}
|
||||
|
||||
private registerShowActivityAction(): void {
|
||||
private registerShowLogAction(): void {
|
||||
const that = this;
|
||||
this._register(registerAction2(class ShowSyncActivityAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: showSyncActivityCommand.id,
|
||||
title: showSyncActivityCommand.title,
|
||||
id: SHOW_SYNC_LOG_COMMAND_ID,
|
||||
title: localize('show sync log title', "Preferences Sync: Show Log"),
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
|
||||
@@ -1058,6 +993,30 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}));
|
||||
}
|
||||
|
||||
private registerViews(): void {
|
||||
const container = this.registerViewContainer();
|
||||
this.registerDataViews(container);
|
||||
}
|
||||
|
||||
private registerViewContainer(): ViewContainer {
|
||||
const viewContainerId = 'workbench.view.sync';
|
||||
return Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
|
||||
{
|
||||
id: viewContainerId,
|
||||
name: localize('sync preferences', "Preferences Sync"),
|
||||
ctorDescriptor: new SyncDescriptor(
|
||||
UserDataSyncViewPaneContainer,
|
||||
[viewContainerId]
|
||||
),
|
||||
icon: Codicon.sync.classNames,
|
||||
hideIfEmpty: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private registerDataViews(container: ViewContainer): void {
|
||||
this._register(this.instantiationService.createInstance(UserDataSyncDataViews, container));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UserDataRemoteContentProvider implements ITextModelContentProvider {
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { getUserDataSyncStore, IUserDataSyncEnablementService, IAuthenticationProvider, isAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
|
||||
type UserAccountClassification = {
|
||||
id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' };
|
||||
};
|
||||
|
||||
type UserAccountEvent = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type AccountQuickPickItem = { label: string, authenticationProvider: IAuthenticationProvider, account?: UserDataSyncAccount, description?: string };
|
||||
|
||||
export class UserDataSyncAccount {
|
||||
|
||||
constructor(readonly authenticationProviderId: string, private readonly session: AuthenticationSession) { }
|
||||
|
||||
get sessionId(): string { return this.session.id; }
|
||||
get accountName(): string { return this.session.account.displayName; }
|
||||
get accountId(): string { return this.session.account.id; }
|
||||
getToken(): Thenable<string> { return this.session.getAccessToken(); }
|
||||
}
|
||||
|
||||
export const enum AccountStatus {
|
||||
Uninitialized = 'uninitialized',
|
||||
Unavailable = 'unavailable',
|
||||
Available = 'available',
|
||||
}
|
||||
|
||||
export class UserDataSyncAccounts extends Disposable {
|
||||
|
||||
private static DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY = 'userDataSyncAccount.donotUseWorkbenchSession';
|
||||
private static CACHED_SESSION_STORAGE_KEY = 'userDataSyncAccountPreference';
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly authenticationProviders: IAuthenticationProvider[];
|
||||
|
||||
private _status: AccountStatus = AccountStatus.Uninitialized;
|
||||
get status(): AccountStatus { return this._status; }
|
||||
private readonly _onDidChangeStatus = this._register(new Emitter<AccountStatus>());
|
||||
readonly onDidChangeStatus = this._onDidChangeStatus.event;
|
||||
|
||||
private readonly _onDidSignOut = this._register(new Emitter<void>());
|
||||
readonly onDidSignOut = this._onDidSignOut.event;
|
||||
|
||||
private _all: Map<string, UserDataSyncAccount[]> = new Map<string, UserDataSyncAccount[]>();
|
||||
get all(): UserDataSyncAccount[] { return flatten(values(this._all)); }
|
||||
|
||||
get current(): UserDataSyncAccount | undefined { return this.all.filter(account => this.isCurrentAccount(account))[0]; }
|
||||
|
||||
constructor(
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IAuthenticationTokenService private readonly authenticationTokenService: IAuthenticationTokenService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IProductService productService: IProductService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
) {
|
||||
super();
|
||||
this.authenticationProviders = getUserDataSyncStore(productService, configurationService)?.authenticationProviders || [];
|
||||
if (this.authenticationProviders.length) {
|
||||
extensionService.whenInstalledExtensionsRegistered().then(() => {
|
||||
if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) {
|
||||
this.initialize();
|
||||
} else {
|
||||
const disposable = this.authenticationService.onDidRegisterAuthenticationProvider(() => {
|
||||
if (this.authenticationProviders.every(({ id }) => authenticationService.isAuthenticationProviderRegistered(id))) {
|
||||
disposable.dispose();
|
||||
this.initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
if (this.currentSessionId === undefined && this.useWorkbenchSessionId && this.environmentService.options?.authenticationSessionId) {
|
||||
this.currentSessionId = this.environmentService.options.authenticationSessionId;
|
||||
this.useWorkbenchSessionId = false;
|
||||
}
|
||||
|
||||
await this.update();
|
||||
|
||||
this._register(
|
||||
Event.any(
|
||||
Event.filter(
|
||||
Event.any(
|
||||
this.authenticationService.onDidRegisterAuthenticationProvider,
|
||||
this.authenticationService.onDidUnregisterAuthenticationProvider,
|
||||
), authenticationProviderId => this.isSupportedAuthenticationProviderId(authenticationProviderId)),
|
||||
this.authenticationTokenService.onTokenFailed)
|
||||
(() => this.update()));
|
||||
|
||||
this._register(Event.filter(this.authenticationService.onDidChangeSessions, e => this.isSupportedAuthenticationProviderId(e.providerId))(({ event }) => this.onDidChangeSessions(event)));
|
||||
this._register(this.storageService.onDidChangeStorage(e => this.onDidChangeStorage(e)));
|
||||
}
|
||||
|
||||
private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean {
|
||||
return this.authenticationProviders.some(({ id }) => id === authenticationProviderId);
|
||||
}
|
||||
|
||||
private async update(): Promise<void> {
|
||||
const allAccounts: Map<string, UserDataSyncAccount[]> = new Map<string, UserDataSyncAccount[]>();
|
||||
for (const { id } of this.authenticationProviders) {
|
||||
const accounts = await this.getAccounts(id);
|
||||
allAccounts.set(id, accounts);
|
||||
}
|
||||
|
||||
this._all = allAccounts;
|
||||
const current = this.current;
|
||||
await this.updateToken(current);
|
||||
this.updateStatus(current);
|
||||
}
|
||||
|
||||
private async getAccounts(authenticationProviderId: string): Promise<UserDataSyncAccount[]> {
|
||||
let accounts: Map<string, UserDataSyncAccount> = new Map<string, UserDataSyncAccount>();
|
||||
let currentAccount: UserDataSyncAccount | null = null;
|
||||
|
||||
const sessions = await this.authenticationService.getSessions(authenticationProviderId) || [];
|
||||
for (const session of sessions) {
|
||||
const account: UserDataSyncAccount = new UserDataSyncAccount(authenticationProviderId, session);
|
||||
accounts.set(account.accountName, account);
|
||||
if (this.isCurrentAccount(account)) {
|
||||
currentAccount = account;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentAccount) {
|
||||
// Always use current account if available
|
||||
accounts.set(currentAccount.accountName, currentAccount);
|
||||
}
|
||||
|
||||
return values(accounts);
|
||||
}
|
||||
|
||||
private async updateToken(current: UserDataSyncAccount | undefined): Promise<void> {
|
||||
let value: { token: string, authenticationProviderId: string } | undefined = undefined;
|
||||
if (current) {
|
||||
try {
|
||||
this.logService.trace('Preferences Sync: Updating the token for the account', current.accountName);
|
||||
const token = await current.getToken();
|
||||
this.logService.trace('Preferences Sync: Token updated for the account', current.accountName);
|
||||
value = { token, authenticationProviderId: current.authenticationProviderId };
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
await this.authenticationTokenService.setToken(value);
|
||||
}
|
||||
|
||||
private updateStatus(current: UserDataSyncAccount | undefined): void {
|
||||
// set status
|
||||
const status: AccountStatus = current ? AccountStatus.Available : AccountStatus.Unavailable;
|
||||
|
||||
if (this._status !== status) {
|
||||
const previous = this._status;
|
||||
this.logService.debug('Sync account status changed', previous, status);
|
||||
|
||||
if (previous === AccountStatus.Available && status === AccountStatus.Unavailable) {
|
||||
this._onDidSignOut.fire();
|
||||
}
|
||||
|
||||
this._status = status;
|
||||
this._onDidChangeStatus.fire(status);
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentAccount(account: UserDataSyncAccount): boolean {
|
||||
return account.sessionId === this.currentSessionId;
|
||||
}
|
||||
|
||||
async pick(): Promise<boolean> {
|
||||
const result = await this.doPick();
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
let sessionId: string, accountName: string, accountId: string;
|
||||
if (isAuthenticationProvider(result)) {
|
||||
const session = await this.authenticationService.login(result.id, result.scopes);
|
||||
sessionId = session.id;
|
||||
accountName = session.account.displayName;
|
||||
accountId = session.account.id;
|
||||
} else {
|
||||
sessionId = result.sessionId;
|
||||
accountName = result.accountName;
|
||||
accountId = result.accountId;
|
||||
}
|
||||
await this.switch(sessionId, accountName, accountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async doPick(): Promise<UserDataSyncAccount | IAuthenticationProvider | undefined> {
|
||||
if (this.authenticationProviders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
await this.update();
|
||||
|
||||
// Single auth provider and no accounts available
|
||||
if (this.authenticationProviders.length === 1 && !this.all.length) {
|
||||
return this.authenticationProviders[0];
|
||||
}
|
||||
|
||||
return new Promise<UserDataSyncAccount | IAuthenticationProvider | undefined>(async (c, e) => {
|
||||
let result: UserDataSyncAccount | IAuthenticationProvider | undefined;
|
||||
const disposables: DisposableStore = new DisposableStore();
|
||||
const quickPick = this.quickInputService.createQuickPick<AccountQuickPickItem>();
|
||||
disposables.add(quickPick);
|
||||
|
||||
quickPick.title = localize('pick an account', "Preferences Sync");
|
||||
quickPick.ok = false;
|
||||
quickPick.placeholder = localize('choose account placeholder', "Select an account");
|
||||
quickPick.ignoreFocusOut = true;
|
||||
quickPick.items = this.createQuickpickItems();
|
||||
|
||||
disposables.add(quickPick.onDidAccept(() => {
|
||||
result = quickPick.selectedItems[0]?.account ? quickPick.selectedItems[0]?.account : quickPick.selectedItems[0]?.authenticationProvider;
|
||||
quickPick.hide();
|
||||
}));
|
||||
disposables.add(quickPick.onDidHide(() => {
|
||||
disposables.dispose();
|
||||
c(result);
|
||||
}));
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
private createQuickpickItems(): (AccountQuickPickItem | IQuickPickSeparator)[] {
|
||||
const quickPickItems: (AccountQuickPickItem | IQuickPickSeparator)[] = [];
|
||||
|
||||
// Signed in Accounts
|
||||
if (this.all.length) {
|
||||
const authenticationProviders = [...this.authenticationProviders].sort(({ id }) => id === this.current?.authenticationProviderId ? -1 : 1);
|
||||
quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") });
|
||||
for (const authenticationProvider of authenticationProviders) {
|
||||
const accounts = (this._all.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.current?.sessionId ? -1 : 1);
|
||||
const providerName = this.authenticationService.getDisplayName(authenticationProvider.id);
|
||||
for (const account of accounts) {
|
||||
quickPickItems.push({
|
||||
label: `${account.accountName} (${providerName})`,
|
||||
description: account.sessionId === this.current?.sessionId ? localize('last used', "Last Used with Sync") : undefined,
|
||||
account,
|
||||
authenticationProvider,
|
||||
});
|
||||
}
|
||||
}
|
||||
quickPickItems.push({ type: 'separator', label: localize('others', "Others") });
|
||||
}
|
||||
|
||||
// Account proviers
|
||||
for (const authenticationProvider of this.authenticationProviders) {
|
||||
const providerName = this.authenticationService.getDisplayName(authenticationProvider.id);
|
||||
quickPickItems.push({ label: localize('sign in using account', "Sign in with {0}", providerName), authenticationProvider });
|
||||
}
|
||||
|
||||
return quickPickItems;
|
||||
}
|
||||
|
||||
private async switch(sessionId: string, accountName: string, accountId: string): Promise<void> {
|
||||
const currentAccount = this.current;
|
||||
if (this.userDataSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) {
|
||||
// accounts are switched while sync is enabled.
|
||||
}
|
||||
this.currentSessionId = sessionId;
|
||||
this.telemetryService.publicLog2<UserAccountEvent, UserAccountClassification>('sync.userAccount', { id: accountId });
|
||||
await this.update();
|
||||
}
|
||||
|
||||
private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void {
|
||||
if (this.currentSessionId && e.removed.includes(this.currentSessionId)) {
|
||||
this.currentSessionId = undefined;
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
private onDidChangeStorage(e: IWorkspaceStorageChangeEvent): void {
|
||||
if (e.key === UserDataSyncAccounts.CACHED_SESSION_STORAGE_KEY && e.scope === StorageScope.GLOBAL
|
||||
&& this.currentSessionId !== this.getStoredCachedSessionId() /* This checks if current window changed the value or not */) {
|
||||
this._cachedCurrentSessionId = null;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
private _cachedCurrentSessionId: string | undefined | null = null;
|
||||
private get currentSessionId(): string | undefined {
|
||||
if (this._cachedCurrentSessionId === null) {
|
||||
this._cachedCurrentSessionId = this.getStoredCachedSessionId();
|
||||
}
|
||||
return this._cachedCurrentSessionId;
|
||||
}
|
||||
|
||||
private set currentSessionId(cachedSessionId: string | undefined) {
|
||||
if (this._cachedCurrentSessionId !== cachedSessionId) {
|
||||
this._cachedCurrentSessionId = cachedSessionId;
|
||||
if (cachedSessionId === undefined) {
|
||||
this.storageService.remove(UserDataSyncAccounts.CACHED_SESSION_STORAGE_KEY, StorageScope.GLOBAL);
|
||||
} else {
|
||||
this.storageService.store(UserDataSyncAccounts.CACHED_SESSION_STORAGE_KEY, cachedSessionId, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredCachedSessionId(): string | undefined {
|
||||
return this.storageService.get(UserDataSyncAccounts.CACHED_SESSION_STORAGE_KEY, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private get useWorkbenchSessionId(): boolean {
|
||||
return !this.storageService.getBoolean(UserDataSyncAccounts.DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY, StorageScope.GLOBAL, false);
|
||||
}
|
||||
|
||||
private set useWorkbenchSessionId(useWorkbenchSession: boolean) {
|
||||
this.storageService.store(UserDataSyncAccounts.DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY, !useWorkbenchSession, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IViewsService } from 'vs/workbench/common/views';
|
||||
import { VIEW_CONTAINER_ID } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncView';
|
||||
|
||||
export class UserDataSyncTrigger extends Disposable {
|
||||
|
||||
@@ -29,7 +28,7 @@ export class UserDataSyncTrigger extends Disposable {
|
||||
Event.filter(
|
||||
Event.any<string | undefined>(
|
||||
Event.map(editorService.onDidActiveEditorChange, () => this.getUserDataEditorInputSource(editorService.activeEditor)),
|
||||
Event.map(Event.filter(viewsService.onDidChangeViewContainerVisibility, e => [VIEWLET_ID, VIEW_CONTAINER_ID].includes(e.id) && e.visible), e => e.id)
|
||||
Event.map(Event.filter(viewsService.onDidChangeViewContainerVisibility, e => e.id === VIEWLET_ID && e.visible), e => e.id)
|
||||
), source => source !== undefined)(source => this._onDidTriggerSync.fire(source!)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, IViewContainersRegistry, ViewContainerLocation, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle, CONTEXT_SYNC_STATE, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService, RawContextKey, ContextKeyExpr, ContextKeyEqualsExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { FolderThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { pad, uppercaseFirstLetter } from 'vs/base/common/strings';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { CONTEXT_ACCOUNT_STATE } from 'vs/workbench/contrib/userDataSync/browser/userDataSync';
|
||||
import { AccountStatus } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncAccount';
|
||||
|
||||
export const VIEW_CONTAINER_ID = 'workbench.view.sync';
|
||||
const CONTEXT_ENABLE_VIEWS = new RawContextKey<boolean>(`showUserDataSyncViews`, false);
|
||||
|
||||
export class UserDataSyncViewContribution implements IWorkbenchContribution {
|
||||
|
||||
private readonly viewsEnablementContext: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
) {
|
||||
const container = this.registerSyncViewContainer();
|
||||
this.viewsEnablementContext = CONTEXT_ENABLE_VIEWS.bindTo(this.contextKeyService);
|
||||
this.registerView(container, true);
|
||||
this.registerView(container, false);
|
||||
}
|
||||
|
||||
private registerSyncViewContainer(): ViewContainer {
|
||||
return Registry.as<IViewContainersRegistry>(Extensions.ViewContainersRegistry).registerViewContainer(
|
||||
{
|
||||
id: VIEW_CONTAINER_ID,
|
||||
name: localize('sync preferences', "Preferences Sync"),
|
||||
ctorDescriptor: new SyncDescriptor(
|
||||
ViewPaneContainer,
|
||||
[VIEW_CONTAINER_ID, { mergeViewWithContainerWhenSingleView: true }]
|
||||
),
|
||||
icon: Codicon.sync.classNames,
|
||||
hideIfEmpty: true,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private registerView(container: ViewContainer, remote: boolean): void {
|
||||
const that = this;
|
||||
const id = `workbench.views.sync.${remote ? 'remote' : 'local'}DataView`;
|
||||
const name = remote ? localize('remote title', "Remote Data") : localize('local title', "Local Backup");
|
||||
const treeView = this.instantiationService.createInstance(TreeView, id, name);
|
||||
treeView.showCollapseAllAction = true;
|
||||
treeView.showRefreshAction = true;
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = new UserDataSyncHistoryViewDataProvider(remote, this.userDataSyncService);
|
||||
}
|
||||
});
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 100,
|
||||
}], container);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}DataView`,
|
||||
title: remote ?
|
||||
{ value: localize('workbench.action.showSyncRemoteBackup', "Show Remote Data"), original: `Show Remote Data` }
|
||||
: { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` },
|
||||
category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` },
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available)),
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
that.viewsEnablementContext.set(true);
|
||||
const viewContainer = viewDescriptorService.getViewContainerByViewId(id);
|
||||
if (viewContainer) {
|
||||
const model = viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (model.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
viewsService.openView(id, true);
|
||||
} else {
|
||||
const disposable = model.onDidChangeActiveViewDescriptors(e => {
|
||||
if (e.added.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
disposable.dispose();
|
||||
viewsService.openView(id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.registerActions(id);
|
||||
}
|
||||
|
||||
private registerActions(viewId: string) {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.resolveResource`,
|
||||
title: localize('workbench.actions.sync.resolveResourceRef', "Show raw JSON sync data"),
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /sync-resource-.*/i))
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
await editorService.openEditor({ resource: URI.parse(handle.$treeItemHandle) });
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.commpareWithLocal`,
|
||||
title: localize('workbench.actions.sync.commpareWithLocal', "Open Changes"),
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const { resource, comparableResource } = <{ resource: string, comparableResource?: string }>JSON.parse(handle.$treeItemHandle);
|
||||
if (comparableResource) {
|
||||
await editorService.openEditor({
|
||||
leftResource: URI.parse(resource),
|
||||
rightResource: URI.parse(comparableResource),
|
||||
options: {
|
||||
preserveFocus: true,
|
||||
revealIfVisible: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await editorService.openEditor({ resource: URI.parse(resource) });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface SyncResourceTreeItem extends ITreeItem {
|
||||
resource: SyncResource;
|
||||
resourceHandle: ISyncResourceHandle;
|
||||
}
|
||||
|
||||
class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
constructor(private readonly remote: boolean, private userDataSyncService: IUserDataSyncService) { }
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (!element) {
|
||||
return ALL_SYNC_RESOURCES.map(resourceKey => ({
|
||||
handle: resourceKey,
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
label: { label: uppercaseFirstLetter(resourceKey) },
|
||||
themeIcon: FolderThemeIcon,
|
||||
}));
|
||||
}
|
||||
const resourceKey = ALL_SYNC_RESOURCES.filter(key => key === element.handle)[0] as SyncResource;
|
||||
if (resourceKey) {
|
||||
const refHandles = this.remote ? await this.userDataSyncService.getRemoteSyncResourceHandles(resourceKey) : await this.userDataSyncService.getLocalSyncResourceHandles(resourceKey);
|
||||
return refHandles.map(({ uri, created }) => {
|
||||
return <SyncResourceTreeItem>{
|
||||
handle: uri.toString(),
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
label: { label: label(new Date(created)) },
|
||||
description: fromNow(created, true),
|
||||
resourceUri: uri,
|
||||
resource: resourceKey,
|
||||
resourceHandle: { uri, created },
|
||||
contextValue: `sync-resource-${resourceKey}`
|
||||
};
|
||||
});
|
||||
}
|
||||
if ((<SyncResourceTreeItem>element).resourceHandle) {
|
||||
const associatedResources = await this.userDataSyncService.getAssociatedResources((<SyncResourceTreeItem>element).resource, (<SyncResourceTreeItem>element).resourceHandle);
|
||||
return associatedResources.map(({ resource, comparableResource }) => {
|
||||
const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource?.toString() });
|
||||
return {
|
||||
handle,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
resourceUri: resource,
|
||||
command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: handle }] },
|
||||
contextValue: `sync-associatedResource-${(<SyncResourceTreeItem>element).resource}`
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function label(date: Date): string {
|
||||
return date.toLocaleDateString() +
|
||||
' ' + pad(date.getHours(), 2) +
|
||||
':' + pad(date.getMinutes(), 2) +
|
||||
':' + pad(date.getSeconds(), 2);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,483 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IViewsRegistry, Extensions, ITreeViewDescriptor, ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState, IViewsService, TreeViewItemHandleArg, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { TreeViewPane, TreeView } from 'vs/workbench/browser/parts/views/treeView';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ALL_SYNC_RESOURCES, SyncResource, IUserDataSyncService, ISyncResourceHandle, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ContextKeyExpr, ContextKeyEqualsExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { FolderThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { pad } from 'vs/base/common/strings';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IUserDataSyncWorkbenchService, CONTEXT_SYNC_STATE, getSyncAreaLabel, CONTEXT_ACCOUNT_STATE, AccountStatus, CONTEXT_ENABLE_VIEWS, SHOW_SYNC_LOG_COMMAND_ID, CONFIGURE_SYNC_COMMAND_ID, ENABLE_SYNC_VIEWS_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
export class UserDataSyncViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
constructor(
|
||||
containerId: string,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
) {
|
||||
super(containerId, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [
|
||||
new Action(SHOW_SYNC_LOG_COMMAND_ID, localize('showLog', "Show Log"), Codicon.output.classNames, true, async () => this.commandService.executeCommand(SHOW_SYNC_LOG_COMMAND_ID)),
|
||||
new Action(CONFIGURE_SYNC_COMMAND_ID, localize('configure', "Configure..."), Codicon.settingsGear.classNames, true, async () => this.commandService.executeCommand(CONFIGURE_SYNC_COMMAND_ID)),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class UserDataSyncDataViews extends Disposable {
|
||||
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IUserDataSyncMachinesService private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
|
||||
) {
|
||||
super();
|
||||
this.registerViews(container);
|
||||
}
|
||||
|
||||
private registerViews(container: ViewContainer): void {
|
||||
const remoteView = this.registerDataView(container, true, true);
|
||||
this.registerRemoteViewActions(remoteView);
|
||||
|
||||
this.registerDataView(container, false, false);
|
||||
this.registerMachinesView(container);
|
||||
}
|
||||
|
||||
private registerDataView(container: ViewContainer, remote: boolean, showByDefault: boolean): TreeView {
|
||||
const id = `workbench.views.sync.${remote ? 'remote' : 'local'}DataView`;
|
||||
const showByDefaultContext = new RawContextKey<boolean>(id, showByDefault);
|
||||
const viewEnablementContext = showByDefaultContext.bindTo(this.contextKeyService);
|
||||
const name = remote ? localize('remote title', "Synced Data") : localize('local title', "Local Backup");
|
||||
const treeView = this.instantiationService.createInstance(TreeView, id, name);
|
||||
treeView.showCollapseAllAction = true;
|
||||
treeView.showRefreshAction = true;
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = remote ? new RemoteUserDataSyncHistoryViewDataProvider(this.userDataSyncService, this.userDataSyncEnablementService, this.userDataSyncMachinesService)
|
||||
: new LocalUserDataSyncHistoryViewDataProvider(this.userDataSyncService, this.userDataSyncEnablementService);
|
||||
}
|
||||
});
|
||||
this._register(Event.any(this.userDataSyncEnablementService.onDidChangeResourceEnablement, this.userDataSyncEnablementService.onDidChangeEnablement)(() => treeView.refresh()));
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS, showByDefaultContext),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 100,
|
||||
}], container);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.showSync${remote ? 'Remote' : 'Local'}DataView`,
|
||||
title: remote ?
|
||||
{ value: localize('workbench.action.showSyncRemoteBackup', "Show Synced Data"), original: `Show Synced Data` }
|
||||
: { value: localize('workbench.action.showSyncLocalBackup', "Show Local Backup"), original: `Show Local Backup` },
|
||||
category: { value: localize('sync preferences', "Preferences Sync"), original: `Preferences Sync` },
|
||||
menu: {
|
||||
id: MenuId.CommandPalette,
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available)),
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
await commandService.executeCommand(ENABLE_SYNC_VIEWS_COMMAND_ID);
|
||||
viewEnablementContext.set(true);
|
||||
|
||||
const viewContainer = viewDescriptorService.getViewContainerByViewId(id);
|
||||
if (viewContainer) {
|
||||
const model = viewDescriptorService.getViewContainerModel(viewContainer);
|
||||
if (model.activeViewDescriptors.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
viewsService.openView(id, true);
|
||||
} else {
|
||||
const disposable = model.onDidChangeActiveViewDescriptors(e => {
|
||||
if (e.added.some(viewDescriptor => viewDescriptor.id === id)) {
|
||||
disposable.dispose();
|
||||
viewsService.openView(id, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.registerDataViewActions(id);
|
||||
return treeView;
|
||||
}
|
||||
|
||||
private registerMachinesView(container: ViewContainer): void {
|
||||
const that = this;
|
||||
const id = `workbench.views.sync.machines`;
|
||||
const name = localize('synced machines', "Synced Machines");
|
||||
const treeView = this.instantiationService.createInstance(TreeView, id, name);
|
||||
treeView.showRefreshAction = true;
|
||||
const disposable = treeView.onDidChangeVisibility(visible => {
|
||||
if (visible && !treeView.dataProvider) {
|
||||
disposable.dispose();
|
||||
treeView.dataProvider = new UserDataSyncMachinesViewDataProvider(treeView, this.userDataSyncMachinesService);
|
||||
}
|
||||
});
|
||||
this._register(Event.any(this.userDataSyncEnablementService.onDidChangeResourceEnablement, this.userDataSyncEnablementService.onDidChangeEnablement)(() => treeView.refresh()));
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([<ITreeViewDescriptor>{
|
||||
id,
|
||||
name,
|
||||
ctorDescriptor: new SyncDescriptor(TreeViewPane),
|
||||
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_ACCOUNT_STATE.isEqualTo(AccountStatus.Available), CONTEXT_ENABLE_VIEWS),
|
||||
canToggleVisibility: true,
|
||||
canMoveView: true,
|
||||
treeView,
|
||||
collapsed: false,
|
||||
order: 200,
|
||||
}], container);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.editCurrentMachineName`,
|
||||
title: localize('workbench.actions.sync.editCurrentMachineName', "Edit Name"),
|
||||
icon: Codicon.edit,
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', id), ContextKeyEqualsExpr.create('viewItem', 'sync-machine')),
|
||||
group: 'inline',
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const inputBox = quickInputService.createInputBox();
|
||||
inputBox.placeholder = localize('placeholder', "Enter the name of the machine");
|
||||
inputBox.show();
|
||||
return new Promise((c, e) => {
|
||||
inputBox.onDidAccept(async () => {
|
||||
const name = inputBox.value;
|
||||
inputBox.dispose();
|
||||
if (name) {
|
||||
try {
|
||||
await that.userDataSyncMachinesService.renameMachine(handle.$treeItemHandle, name);
|
||||
await treeView.refresh();
|
||||
c();
|
||||
} catch (error) {
|
||||
e(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerDataViewActions(viewId: string) {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.resolveResource`,
|
||||
title: localize('workbench.actions.sync.resolveResourceRef', "Show raw JSON sync data"),
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /sync-resource-.*/i))
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const { resource } = <{ resource: string }>JSON.parse(handle.$treeItemHandle);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
await editorService.openEditor({ resource: URI.parse(resource) });
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.replaceCurrent`,
|
||||
title: localize('workbench.actions.sync.replaceCurrent', "Download..."),
|
||||
icon: { id: 'codicon/cloud-download' },
|
||||
menu: {
|
||||
id: MenuId.ViewItemContext,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', viewId), ContextKeyExpr.regex('viewItem', /sync-resource-.*/i)),
|
||||
group: 'inline',
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const { resource, syncResource } = <{ resource: string, syncResource: SyncResource }>JSON.parse(handle.$treeItemHandle);
|
||||
const result = await dialogService.confirm({
|
||||
message: localize('confirm replace', "Would you like to replace your current {0} with selected?", getSyncAreaLabel(syncResource)),
|
||||
type: 'info',
|
||||
title: localize('preferences sync', "Preferences Sync")
|
||||
});
|
||||
if (result.confirmed) {
|
||||
return userDataSyncService.replace(URI.parse(resource));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.sync.commpareWithLocal`,
|
||||
title: localize('workbench.actions.sync.commpareWithLocal', "Open Changes"),
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor, handle: TreeViewItemHandleArg): Promise<void> {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const { resource, comparableResource } = <{ resource: string, comparableResource?: string }>JSON.parse(handle.$treeItemHandle);
|
||||
if (comparableResource) {
|
||||
await editorService.openEditor({
|
||||
leftResource: URI.parse(resource),
|
||||
rightResource: URI.parse(comparableResource),
|
||||
options: {
|
||||
preserveFocus: true,
|
||||
revealIfVisible: true,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await editorService.openEditor({ resource: URI.parse(resource) });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerRemoteViewActions(view: TreeView) {
|
||||
this.registerResetAction(view);
|
||||
}
|
||||
|
||||
private registerResetAction(view: TreeView) {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.actions.syncData.reset`,
|
||||
title: localize('workbench.actions.syncData.reset', "Reset Synced Data"),
|
||||
menu: {
|
||||
id: MenuId.ViewTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', view.id)),
|
||||
},
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor): Promise<void> {
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
const userDataSyncWorkbenchService = accessor.get(IUserDataSyncWorkbenchService);
|
||||
const result = await dialogService.confirm({
|
||||
message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."),
|
||||
title: localize('reset title', "Reset Synced Data"),
|
||||
type: 'info',
|
||||
primaryButton: localize('reset button', "Reset"),
|
||||
});
|
||||
if (result.confirmed) {
|
||||
await userDataSyncWorkbenchService.turnoff(true);
|
||||
await view.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface SyncResourceTreeItem extends ITreeItem {
|
||||
resource: SyncResource;
|
||||
resourceHandle: ISyncResourceHandle;
|
||||
}
|
||||
|
||||
abstract class UserDataSyncHistoryViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
constructor(
|
||||
protected readonly userDataSyncService: IUserDataSyncService,
|
||||
private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
) { }
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (!element) {
|
||||
return this.getRoots();
|
||||
}
|
||||
const syncResource = ALL_SYNC_RESOURCES.filter(key => key === element.handle)[0] as SyncResource;
|
||||
if (syncResource) {
|
||||
return this.getChildrenForSyncResource(syncResource);
|
||||
}
|
||||
if ((<SyncResourceTreeItem>element).resourceHandle) {
|
||||
return this.getChildrenForSyncResourceTreeItem(<SyncResourceTreeItem>element);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected async getRoots(): Promise<ITreeItem[]> {
|
||||
return ALL_SYNC_RESOURCES.map(resourceKey => ({
|
||||
handle: resourceKey,
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
label: { label: getSyncAreaLabel(resourceKey) },
|
||||
description: !this.userDataSyncEnablementService.isEnabled() || this.userDataSyncEnablementService.isResourceEnabled(resourceKey) ? undefined : localize('not syncing', "Not syncing"),
|
||||
themeIcon: FolderThemeIcon,
|
||||
contextValue: resourceKey
|
||||
}));
|
||||
}
|
||||
|
||||
protected async getChildrenForSyncResource(syncResource: SyncResource): Promise<ITreeItem[]> {
|
||||
const refHandles = await this.getSyncResourceHandles(syncResource);
|
||||
return refHandles.map(({ uri, created }) => {
|
||||
const handle = JSON.stringify({ resource: uri.toString(), syncResource });
|
||||
return <SyncResourceTreeItem>{
|
||||
handle,
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
label: { label: label(new Date(created)) },
|
||||
description: fromNow(created, true),
|
||||
resourceUri: uri,
|
||||
resource: syncResource,
|
||||
resourceHandle: { uri, created },
|
||||
contextValue: `sync-resource-${syncResource}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected async getChildrenForSyncResourceTreeItem(element: SyncResourceTreeItem): Promise<ITreeItem[]> {
|
||||
const associatedResources = await this.userDataSyncService.getAssociatedResources((<SyncResourceTreeItem>element).resource, (<SyncResourceTreeItem>element).resourceHandle);
|
||||
return associatedResources.map(({ resource, comparableResource }) => {
|
||||
const handle = JSON.stringify({ resource: resource.toString(), comparableResource: comparableResource?.toString() });
|
||||
return {
|
||||
handle,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
resourceUri: resource,
|
||||
command: { id: `workbench.actions.sync.commpareWithLocal`, title: '', arguments: [<TreeViewItemHandleArg>{ $treeViewId: '', $treeItemHandle: handle }] },
|
||||
contextValue: `sync-associatedResource-${(<SyncResourceTreeItem>element).resource}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract getSyncResourceHandles(syncResource: SyncResource): Promise<ISyncResourceHandle[]>;
|
||||
}
|
||||
|
||||
class LocalUserDataSyncHistoryViewDataProvider extends UserDataSyncHistoryViewDataProvider {
|
||||
|
||||
protected getSyncResourceHandles(syncResource: SyncResource): Promise<ISyncResourceHandle[]> {
|
||||
return this.userDataSyncService.getLocalSyncResourceHandles(syncResource);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteUserDataSyncHistoryViewDataProvider extends UserDataSyncHistoryViewDataProvider {
|
||||
|
||||
private machinesPromise: Promise<IUserDataSyncMachine[]> | undefined;
|
||||
|
||||
constructor(
|
||||
userDataSyncService: IUserDataSyncService,
|
||||
userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
|
||||
) {
|
||||
super(userDataSyncService, userDataSyncEnablementService);
|
||||
}
|
||||
|
||||
async getChildren(element?: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (!element) {
|
||||
this.machinesPromise = undefined;
|
||||
}
|
||||
return super.getChildren(element);
|
||||
}
|
||||
|
||||
private getMachines(): Promise<IUserDataSyncMachine[]> {
|
||||
if (this.machinesPromise === undefined) {
|
||||
this.machinesPromise = this.userDataSyncMachinesService.getMachines();
|
||||
}
|
||||
return this.machinesPromise;
|
||||
}
|
||||
|
||||
protected getSyncResourceHandles(syncResource: SyncResource): Promise<ISyncResourceHandle[]> {
|
||||
return this.userDataSyncService.getRemoteSyncResourceHandles(syncResource);
|
||||
}
|
||||
|
||||
protected async getChildrenForSyncResourceTreeItem(element: SyncResourceTreeItem): Promise<ITreeItem[]> {
|
||||
const children = await super.getChildrenForSyncResourceTreeItem(element);
|
||||
const machineId = await this.userDataSyncService.getMachineId(element.resource, element.resourceHandle);
|
||||
if (machineId) {
|
||||
const machines = await this.getMachines();
|
||||
const machine = machines.find(({ id }) => id === machineId);
|
||||
children.push({
|
||||
handle: machineId,
|
||||
label: { label: machine?.name || machineId },
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
themeIcon: Codicon.vm,
|
||||
});
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
class UserDataSyncMachinesViewDataProvider implements ITreeViewDataProvider {
|
||||
|
||||
constructor(
|
||||
private readonly treeView: TreeView,
|
||||
private readonly userDataSyncMachinesService: IUserDataSyncMachinesService,
|
||||
) { }
|
||||
|
||||
async getChildren(): Promise<ITreeItem[]> {
|
||||
let machines = await this.userDataSyncMachinesService.getMachines();
|
||||
machines = machines.filter(m => !m.disabled).sort((m1, m2) => m1.isCurrent ? -1 : 1);
|
||||
this.treeView.message = machines.length ? undefined : localize('no machines', "No Machines");
|
||||
return machines.map(({ id, name, isCurrent }) => ({
|
||||
handle: id,
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
label: { label: name },
|
||||
description: isCurrent ? localize({ key: 'current', comment: ['Current machine'] }, "Current") : undefined,
|
||||
themeIcon: Codicon.vm,
|
||||
contextValue: 'sync-machine'
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function label(date: Date): string {
|
||||
return date.toLocaleDateString() +
|
||||
' ' + pad(date.getHours(), 2) +
|
||||
':' + pad(date.getMinutes(), 2) +
|
||||
':' + pad(date.getSeconds(), 2);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IUserDataSyncUtilService, CONTEXT_SYNC_STATE, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncUtilService, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
@@ -14,7 +14,13 @@ import { localize } from 'vs/nls';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { CONTEXT_SYNC_STATE, SHOW_SYNC_LOG_COMMAND_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
|
||||
class UserDataSyncServicesContribution implements IWorkbenchContribution {
|
||||
|
||||
@@ -26,8 +32,39 @@ class UserDataSyncServicesContribution implements IWorkbenchContribution {
|
||||
}
|
||||
}
|
||||
|
||||
class UserDataSyncReportIssueContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IWorkbenchIssueService private readonly workbenchIssueService: IWorkbenchIssueService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super();
|
||||
this._register(userDataAutoSyncService.onError(error => this.onAutoSyncError(error)));
|
||||
}
|
||||
|
||||
private onAutoSyncError(error: UserDataSyncError): void {
|
||||
switch (error.code) {
|
||||
case UserDataSyncErrorCode.LocalTooManyRequests:
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too many requests', "Turned off syncing preferences on this device because it is making too many requests. Please report an issue by providing the sync logs."),
|
||||
actions: {
|
||||
primary: [
|
||||
new Action('Show Sync Logs', localize('show sync logs', "Show Log"), undefined, true, () => this.commandService.executeCommand(SHOW_SYNC_LOG_COMMAND_ID)),
|
||||
new Action('Report Issue', localize('report issue', "Report Issue"), undefined, true, () => this.workbenchIssueService.openReporter())
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(UserDataSyncServicesContribution, LifecyclePhase.Starting);
|
||||
workbenchRegistry.registerWorkbenchContribution(UserDataSyncReportIssueContribution, LifecyclePhase.Restored);
|
||||
|
||||
registerAction2(class OpenSyncBackupsFolder extends Action2 {
|
||||
constructor() {
|
||||
|
||||
Reference in New Issue
Block a user