Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5

This commit is contained in:
ADS Merger
2020-02-08 04:50:58 +00:00
parent 8c61538a27
commit 2af13c18d2
752 changed files with 16458 additions and 10063 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { localize } from 'vs/nls';
import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
@@ -23,13 +23,12 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { isWeb } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync';
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
import { timeout } from 'vs/base/common/async';
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { Session } from 'vs/editor/common/modes';
import { AuthenticationSession } from 'vs/editor/common/modes';
import { isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
@@ -49,7 +48,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
const enum AuthStatus {
Initializing = 'Initializing',
SignedIn = 'SignedIn',
SignedOut = 'SignedOut'
SignedOut = 'SignedOut',
Unavailable = 'Unavailable'
}
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
@@ -68,10 +68,6 @@ type FirstTimeSyncClassification = {
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
type SyncErrorClassification = {
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private static readonly ENABLEMENT_SETTING = 'sync.enable';
@@ -82,7 +78,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private readonly badgeDisposable = this._register(new MutableDisposable());
private readonly conflictsWarningDisposable = this._register(new MutableDisposable());
private readonly signInNotificationDisposable = this._register(new MutableDisposable());
private _activeAccount: Session | undefined;
private _activeAccount: AuthenticationSession | undefined;
constructor(
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@@ -118,9 +114,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source)));
this.registerActions();
this.initializeActiveAccount().then(_ => {
if (isWeb) {
this._register(instantiationService.createInstance(UserDataAutoSync));
} else {
if (!isWeb) {
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync()));
}
});
@@ -131,44 +125,66 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
private async initializeActiveAccount(): Promise<void> {
const accounts = await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId);
const sessions = await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId);
// Auth provider has not yet been registered
if (!accounts) {
if (!sessions) {
return;
}
if (accounts.length === 0) {
this.activeAccount = undefined;
if (sessions.length === 0) {
this.setActiveAccount(undefined);
return;
}
if (accounts.length === 1) {
this.activeAccount = accounts[0];
if (sessions.length === 1) {
this.logAuthenticatedEvent(sessions[0]);
this.setActiveAccount(sessions[0]);
return;
}
const selectedAccount = await this.quickInputService.pick(accounts.map(account => {
const selectedAccount = await this.quickInputService.pick(sessions.map(session => {
return {
id: account.id,
label: account.displayName
id: session.id,
label: session.accountName
};
}), { canPickMany: false });
if (selectedAccount) {
this.activeAccount = accounts.filter(account => selectedAccount.id === account.id)[0];
const selected = sessions.filter(account => selectedAccount.id === account.id)[0];
this.logAuthenticatedEvent(selected);
this.setActiveAccount(selected);
}
}
get activeAccount(): Session | undefined {
private logAuthenticatedEvent(session: AuthenticationSession): void {
type UserAuthenticatedClassification = {
id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' };
};
type UserAuthenticatedEvent = {
id: string;
};
const id = session.id.split('/')[1];
this.telemetryService.publicLog2<UserAuthenticatedEvent, UserAuthenticatedClassification>('user.authenticated', { id });
}
get activeAccount(): AuthenticationSession | undefined {
return this._activeAccount;
}
set activeAccount(account: Session | undefined) {
async setActiveAccount(account: AuthenticationSession | undefined) {
this._activeAccount = account;
if (account) {
this.userDataAuthTokenService.setToken(account.accessToken);
this.authenticationState.set(AuthStatus.SignedIn);
try {
const token = await account.accessToken();
this.userDataAuthTokenService.setToken(token);
this.authenticationState.set(AuthStatus.SignedIn);
} catch (e) {
this.userDataAuthTokenService.setToken(undefined);
this.authenticationState.set(AuthStatus.Unavailable);
}
} else {
this.userDataAuthTokenService.setToken(undefined);
this.authenticationState.set(AuthStatus.SignedOut);
@@ -183,7 +199,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
// Try to update existing account, case where access token has been refreshed
const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []);
const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0];
this.activeAccount = matchingAccount;
this.setActiveAccount(matchingAccount);
} else {
this.initializeActiveAccount();
}
@@ -198,7 +214,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private onDidUnregisterAuthenticationProvider(providerId: string) {
if (providerId === this.userDataSyncStore!.authenticationProviderId) {
this.activeAccount = undefined;
this.setActiveAccount(undefined);
this.authenticationState.reset();
}
}
@@ -263,27 +279,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void {
switch (code) {
case UserDataSyncErrorCode.TooManyFailures:
this.telemetryService.publicLog2('sync/errorTooMany');
this.disableSync();
this.notificationService.notify({
severity: Severity.Error,
message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."),
actions: {
primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())]
}
});
return;
case UserDataSyncErrorCode.TooLarge:
this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source: source! });
if (source === SyncSource.Keybindings || source === SyncSource.Settings) {
const sourceArea = getSyncAreaLabel(source);
this.disableSync();
this.notificationService.notify({
severity: Severity.Error,
message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'),
message: localize('too large', "Disabled synchronizing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'),
actions: {
primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true,
primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true,
() => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
}
});
@@ -368,7 +371,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}, {
id: 'sync.enableUIState',
label: getSyncAreaLabel(SyncSource.GlobalState),
description: localize('ui state description', "Display Language (Only)")
description: localize('ui state description', "only 'Display Language' for now")
}];
}
@@ -427,13 +430,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
],
{
cancelId: 1,
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from cloud?"),
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\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' });
await this.userDataSyncService.sync();
break;
case 1:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
@@ -464,17 +466,26 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
if (result.checkboxChecked) {
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
await this.userDataSyncService.reset();
} else {
await this.userDataSyncService.resetLocal();
}
}
}
private disableSync(): Promise<void> {
return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER);
private disableSync(source?: SyncSource): Promise<void> {
let key: string = UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING;
switch (source) {
case SyncSource.Settings: key = 'sync.enableSettings'; break;
case SyncSource.Keybindings: key = 'sync.enableKeybindings'; break;
case SyncSource.Extensions: key = 'sync.enableExtensions'; break;
case SyncSource.GlobalState: key = 'sync.enableUIState'; break;
}
return this.configurationService.updateValue(key, false, ConfigurationTarget.USER);
}
private async signIn(): Promise<void> {
try {
this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']);
this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']));
} catch (e) {
this.notificationService.error(e);
throw e;
@@ -484,7 +495,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private async signOut(): Promise<void> {
if (this.activeAccount) {
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id);
this.activeAccount = undefined;
this.setActiveAccount(undefined);
}
}
@@ -671,10 +682,10 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider {
provideTextContent(uri: URI): Promise<ITextModel> | null {
let promise: Promise<string | null> | undefined;
if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings);
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings, true);
}
if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings);
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings, true);
}
if (promise) {
return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri));
@@ -759,35 +770,40 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
private createAcceptChangesWidgetRenderer(): void {
if (!this.acceptChangesButton) {
const replaceLabel = localize('accept remote', "Replace (Overwrite Local)");
const applyLabel = localize('accept local', "Apply");
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? replaceLabel : applyLabel, null);
const acceptRemoteLabel = localize('accept remote', "Accept Remote");
const acceptLocalLabel = localize('accept local', "Accept Local");
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? acceptRemoteLabel : acceptLocalLabel, null);
this._register(this.acceptChangesButton.onClick(async () => {
const model = this.editor.getModel();
if (model) {
const conflictsSource = this.userDataSyncService.conflictsSource;
const syncSource = getSyncSourceFromRemoteContentResource(model.uri);
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'replaceLocal' : 'apply' });
if (syncSource !== undefined) {
const syncAreaLabel = getSyncAreaLabel(syncSource);
const result = await this.dialogService.confirm({
type: 'info',
title: localize('Sync overwrite local', "Sync: {0}", replaceLabel),
message: localize('confirm replace and overwrite local', "Would you like to replace Local {0} with Remote {1}?", syncAreaLabel, syncAreaLabel),
primaryButton: replaceLabel
});
if (!result.confirmed) {
return;
}
}
try {
await this.userDataSyncService.resolveConflictsAndContinueSync(model.getValue());
} catch (e) {
this.userDataSyncService.restart().then(() => {
if (conflictsSource === this.userDataSyncService.conflictsSource) {
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'acceptRemote' : 'acceptLocal' });
const syncAreaLabel = getSyncAreaLabel(conflictsSource!);
const result = await this.dialogService.confirm({
type: 'info',
title: syncSource !== undefined
? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel)
: localize('Sync accept local', "Sync: {0}", acceptLocalLabel),
message: syncSource !== undefined
? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel, syncAreaLabel)
: localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel),
primaryButton: syncSource !== undefined ? acceptRemoteLabel : acceptLocalLabel
});
if (result.confirmed) {
try {
await this.userDataSyncService.accept(conflictsSource!, model.getValue());
} catch (e) {
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.NewLocal) {
this.userDataSyncService.restart().then(() => {
if (conflictsSource === this.userDataSyncService.conflictsSource) {
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
}
});
} else {
this.notificationService.error(e);
}
});
}
}
}
}));