Merge pull request #104996 from microsoft/sandy081/initUserData
Initialise user data from Settings sync service in Web
This commit is contained in:
commit
860ed7b6cf
|
@ -5,15 +5,16 @@
|
|||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
|
||||
|
||||
export interface ICredentialsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
export interface ICredentialsProvider {
|
||||
getPassword(service: string, account: string): Promise<string | null>;
|
||||
setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
deletePassword(service: string, account: string): Promise<boolean>;
|
||||
findPassword(service: string): Promise<string | null>;
|
||||
findCredentials(service: string): Promise<Array<{ account: string, password: string }>>;
|
||||
}
|
||||
|
||||
export const ICredentialsService = createDecorator<ICredentialsService>('ICredentialsService');
|
||||
|
||||
export interface ICredentialsService extends ICredentialsProvider {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,10 @@ function isSyncData(thing: any): thing is ISyncData {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getLastSyncResourceUri(syncResource: SyncResource, environmentService: IEnvironmentService): URI {
|
||||
return joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
|
||||
}
|
||||
|
||||
export interface IResourcePreview {
|
||||
|
||||
readonly remoteResource: URI;
|
||||
|
@ -133,7 +137,7 @@ export abstract class AbstractSynchroniser extends Disposable {
|
|||
this.syncResourceLogLabel = uppercaseFirstLetter(this.resource);
|
||||
this.syncFolder = joinPath(environmentService.userDataSyncHome, resource);
|
||||
this.syncPreviewFolder = joinPath(this.syncFolder, PREVIEW_DIR_NAME);
|
||||
this.lastSyncResource = joinPath(this.syncFolder, `lastSync${this.resource}.json`);
|
||||
this.lastSyncResource = getLastSyncResourceUri(resource, environmentService);
|
||||
this.currentMachineIdPromise = getServiceMachineId(environmentService, fileService, storageService);
|
||||
}
|
||||
|
||||
|
@ -797,3 +801,62 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class AbstractInitializer {
|
||||
|
||||
private readonly lastSyncResource: URI;
|
||||
|
||||
constructor(
|
||||
readonly resource: SyncResource,
|
||||
@IEnvironmentService protected readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
) {
|
||||
this.lastSyncResource = getLastSyncResourceUri(this.resource, environmentService);
|
||||
}
|
||||
|
||||
async initialize({ ref, content }: IUserData): Promise<void> {
|
||||
if (!content) {
|
||||
this.logService.info('Remote content does not exist.', this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
const syncData = this.parseSyncData(content);
|
||||
if (!syncData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isPreviouslySynced = await this.fileService.exists(this.lastSyncResource);
|
||||
if (isPreviouslySynced) {
|
||||
this.logService.info('Remote content does not exist.', this.resource);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.doInitialize({ ref, syncData });
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
private parseSyncData(content: string): ISyncData | undefined {
|
||||
try {
|
||||
const syncData: ISyncData = JSON.parse(content);
|
||||
if (isSyncData(syncData)) {
|
||||
return syncData;
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
this.logService.info('Cannot parse sync data as it is not compatible with the current version.', this.resource);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
|
||||
const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: lastSyncRemoteUserData.syncData ? JSON.stringify(lastSyncRemoteUserData.syncData) : null, ...additionalProps };
|
||||
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
|
||||
}
|
||||
|
||||
protected abstract doInitialize(remoteUserData: IRemoteUserData): Promise<void>;
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
|
|||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { merge, getIgnoredExtensions } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, dirname, basename, isEqual } from 'vs/base/common/resources';
|
||||
|
@ -42,6 +42,34 @@ interface ILastSyncUserData extends IRemoteUserData {
|
|||
skippedExtensions: ISyncExtension[] | undefined;
|
||||
}
|
||||
|
||||
async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagementService: IExtensionManagementService): Promise<ISyncExtension[]> {
|
||||
const extensions = JSON.parse(syncData.content);
|
||||
if (syncData.version === 1
|
||||
|| syncData.version === 2
|
||||
) {
|
||||
const systemExtensions = await extensionManagementService.getInstalled(ExtensionType.System);
|
||||
for (const extension of extensions) {
|
||||
// #region Migration from v1 (enabled -> disabled)
|
||||
if (syncData.version === 1) {
|
||||
if ((<any>extension).enabled === false) {
|
||||
extension.disabled = true;
|
||||
}
|
||||
delete (<any>extension).enabled;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Migration from v2 (set installed property on extension)
|
||||
if (syncData.version === 2) {
|
||||
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
|
||||
extension.installed = true;
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` });
|
||||
|
@ -84,9 +112,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
}
|
||||
|
||||
protected async generateSyncPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<IExtensionResourcePreview[]> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await this.parseAndMigrateExtensions(remoteUserData.syncData) : null;
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await this.parseAndMigrateExtensions(lastSyncUserData.syncData!) : null;
|
||||
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? await parseAndMigrateExtensions(lastSyncUserData.syncData!, this.extensionManagementService) : null;
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const localExtensions = this.getLocalExtensions(installedExtensions);
|
||||
|
@ -385,34 +413,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
return newSkippedExtensions;
|
||||
}
|
||||
|
||||
private async parseAndMigrateExtensions(syncData: ISyncData): Promise<ISyncExtension[]> {
|
||||
const extensions = this.parseExtensions(syncData);
|
||||
if (syncData.version === 1
|
||||
|| syncData.version === 2
|
||||
) {
|
||||
const systemExtensions = await this.extensionManagementService.getInstalled(ExtensionType.System);
|
||||
for (const extension of extensions) {
|
||||
// #region Migration from v1 (enabled -> disabled)
|
||||
if (syncData.version === 1) {
|
||||
if ((<any>extension).enabled === false) {
|
||||
extension.disabled = true;
|
||||
}
|
||||
delete (<any>extension).enabled;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
// #region Migration from v2 (set installed property on extension)
|
||||
if (syncData.version === 2) {
|
||||
if (systemExtensions.every(installed => !areSameExtensions(installed.identifier, extension.identifier))) {
|
||||
extension.installed = true;
|
||||
}
|
||||
}
|
||||
// #endregion
|
||||
}
|
||||
}
|
||||
return extensions;
|
||||
}
|
||||
|
||||
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
|
@ -433,3 +433,68 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export class ExtensionsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Extensions, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? await parseAndMigrateExtensions(remoteUserData.syncData, this.extensionManagementService) : null;
|
||||
if (!remoteExtensions) {
|
||||
this.logService.info('Skipping initializing extensions because remote extensions does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
|
||||
const toDisable: IExtensionIdentifier[] = [];
|
||||
for (const extension of remoteExtensions) {
|
||||
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
|
||||
if (extension.disabled) {
|
||||
toDisable.push(extension.identifier);
|
||||
}
|
||||
} else {
|
||||
if (extension.installed) {
|
||||
if (extension.identifier.uuid) {
|
||||
toInstall.uuids.push(extension.identifier.uuid);
|
||||
} else {
|
||||
toInstall.names.push(extension.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toInstall.names.length || toInstall.uuids.length) {
|
||||
const galleryExtensions = (await this.galleryService.query({ ids: toInstall.uuids, names: toInstall.names, pageSize: toInstall.uuids.length + toInstall.names.length }, CancellationToken.None)).firstPage;
|
||||
for (const galleryExtension of galleryExtensions) {
|
||||
try {
|
||||
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension);
|
||||
this.logService.info(`Installed extension.`, galleryExtension.identifier.id);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toDisable.length) {
|
||||
for (const identifier of toDisable) {
|
||||
this.logService.trace(`Enabling extension...`, identifier.id);
|
||||
await this.extensionEnablementService.disableExtension(identifier);
|
||||
this.logService.info(`Enabled extension`, identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
|
|||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { parse } from 'vs/base/common/json';
|
||||
import { AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IMergeResult, IResourcePreview } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -341,3 +341,55 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
|||
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
|
||||
}
|
||||
}
|
||||
|
||||
export class GlobalStateInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.GlobalState, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
if (!remoteGlobalState) {
|
||||
this.logService.info('Skipping initializing global state because remote global state does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const argv: IStringDictionary<any> = {};
|
||||
const storage: IStringDictionary<any> = {};
|
||||
for (const key of Object.keys(remoteGlobalState.storage)) {
|
||||
if (key.startsWith(argvStoragePrefx)) {
|
||||
argv[key.substring(argvStoragePrefx.length)] = remoteGlobalState.storage[key].value;
|
||||
} else {
|
||||
if (this.storageService.get(key, StorageScope.GLOBAL) === undefined) {
|
||||
storage[key] = remoteGlobalState.storage[key].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(argv).length) {
|
||||
let content = '{}';
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.argvResource);
|
||||
content = fileContent.value.toString();
|
||||
} catch (error) { }
|
||||
for (const argvProperty of Object.keys(argv)) {
|
||||
content = edit(content, [argvProperty], argv[argvProperty], {});
|
||||
}
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
|
||||
}
|
||||
|
||||
if (Object.keys(storage).length) {
|
||||
for (const key of Object.keys(storage)) {
|
||||
this.storageService.store(key, storage[key], StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,12 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
|||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { joinPath, isEqual, dirname, basename } from 'vs/base/common/resources';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
|
@ -35,6 +36,21 @@ interface IKeybindingsResourcePreview extends IFileResourcePreview {
|
|||
previewResult: IMergeResult;
|
||||
}
|
||||
|
||||
export function getKeybindingsContentFromSyncContent(syncContent: string, platformSpecific: boolean): string | null {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (platformSpecific) {
|
||||
return isUndefined(parsed.all) ? null : parsed.all;
|
||||
}
|
||||
switch (OS) {
|
||||
case OperatingSystem.Macintosh:
|
||||
return isUndefined(parsed.mac) ? null : parsed.mac;
|
||||
case OperatingSystem.Linux:
|
||||
return isUndefined(parsed.linux) ? null : parsed.linux;
|
||||
case OperatingSystem.Windows:
|
||||
return isUndefined(parsed.windows) ? null : parsed.windows;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
|
||||
|
@ -266,20 +282,9 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
return null;
|
||||
}
|
||||
|
||||
getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
const parsed = <ISyncContent>JSON.parse(syncContent);
|
||||
if (!this.syncKeybindingsPerPlatform()) {
|
||||
return isUndefined(parsed.all) ? null : parsed.all;
|
||||
}
|
||||
switch (OS) {
|
||||
case OperatingSystem.Macintosh:
|
||||
return isUndefined(parsed.mac) ? null : parsed.mac;
|
||||
case OperatingSystem.Linux:
|
||||
return isUndefined(parsed.linux) ? null : parsed.linux;
|
||||
case OperatingSystem.Windows:
|
||||
return isUndefined(parsed.windows) ? null : parsed.windows;
|
||||
}
|
||||
return getKeybindingsContentFromSyncContent(syncContent, this.syncKeybindingsPerPlatform());
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return null;
|
||||
|
@ -325,3 +330,52 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export class KeybindingsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Keybindings, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const keybindingsContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
|
||||
if (!keybindingsContent) {
|
||||
this.logService.info('Skipping initializing keybindings because remote keybindings does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing keybindings because local keybindings exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(keybindingsContent));
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
const keybindings = parse(fileContent.value.toString());
|
||||
return !isNonEmptyArray(keybindings);
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
private getKeybindingsContentFromSyncContent(syncContent: string): string | null {
|
||||
try {
|
||||
return getKeybindingsContentFromSyncContent(syncContent, true);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -275,8 +275,11 @@ export function areSame(localContent: string, remoteContent: string, ignoredSett
|
|||
}
|
||||
|
||||
export function isEmpty(content: string): boolean {
|
||||
const nodes = parseSettings(content);
|
||||
return nodes.length === 0;
|
||||
if (content) {
|
||||
const nodes = parseSettings(content);
|
||||
return nodes.length === 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any> | null, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
|
|
|
@ -17,7 +17,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { updateIgnoredSettings, merge, getIgnoredSettings, isEmpty } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { edit } from 'vs/platform/userDataSync/common/content';
|
||||
import { AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
|
@ -40,6 +40,11 @@ function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
|
|||
&& Object.keys(thing).length === 1;
|
||||
}
|
||||
|
||||
export function parseSettingsSyncContent(syncContent: string): ISettingsSyncContent {
|
||||
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
|
||||
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
|
||||
}
|
||||
|
||||
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements IUserDataSynchroniser {
|
||||
|
||||
/* Version 2: Change settings from `sync.${setting}` to `settingsSync.{setting}` */
|
||||
|
@ -281,10 +286,9 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
|||
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
|
||||
}
|
||||
|
||||
parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
try {
|
||||
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
|
||||
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
|
||||
return parseSettingsSyncContent(syncContent);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
@ -350,6 +354,54 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
|
|||
}
|
||||
}
|
||||
|
||||
export class SettingsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Settings, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const settingsSyncContent = remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
|
||||
if (!settingsSyncContent) {
|
||||
this.logService.info('Skipping initializing settings because remote settings does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing settings because local settings exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(settingsSyncContent.settings));
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(this.environmentService.settingsResource);
|
||||
return isEmpty(fileContent.value.toString().trim());
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
|
||||
try {
|
||||
return parseSettingsSyncContent(syncContent);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function isSyncData(thing: any): thing is ISyncData {
|
||||
if (thing
|
||||
&& (thing.version !== undefined && typeof thing.version === 'number')
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService, FileChangesEvent, IFileStat, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { AbstractInitializer, AbstractSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
@ -499,3 +499,49 @@ export class SnippetsSynchroniser extends AbstractSynchroniser implements IUserD
|
|||
return snippets;
|
||||
}
|
||||
}
|
||||
|
||||
export class SnippetsInitializer extends AbstractInitializer {
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
) {
|
||||
super(SyncResource.Snippets, environmentService, logService, fileService);
|
||||
}
|
||||
|
||||
async doInitialize(remoteUserData: IRemoteUserData): Promise<void> {
|
||||
const remoteSnippets: IStringDictionary<string> | null = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
if (!remoteSnippets) {
|
||||
this.logService.info('Skipping initializing snippets because remote snippets does not exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEmpty = await this.isEmpty();
|
||||
if (!isEmpty) {
|
||||
this.logService.info('Skipping initializing snippets because local snippets exist.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(remoteSnippets)) {
|
||||
const content = remoteSnippets[key];
|
||||
if (content) {
|
||||
const resource = joinPath(this.environmentService.snippetsHome, key);
|
||||
await this.fileService.createFile(resource, VSBuffer.fromString(content));
|
||||
this.logService.info('Created snippet', basename(resource));
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateLastSyncUserData(remoteUserData);
|
||||
}
|
||||
|
||||
private async isEmpty(): Promise<boolean> {
|
||||
try {
|
||||
const stat = await this.fileService.resolve(this.environmentService.snippetsHome);
|
||||
return !stat.children?.length;
|
||||
} catch (error) {
|
||||
return (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -164,10 +164,7 @@ export interface IUserDataSyncStoreManagementService {
|
|||
getPreviousUserDataSyncStore(): Promise<IUserDataSyncStore | undefined>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
export interface IUserDataSyncStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
export interface IUserDataSyncStoreClient {
|
||||
readonly onDidChangeDonotMakeRequestsUntil: Event<void>;
|
||||
readonly donotMakeRequestsUntil: Date | undefined;
|
||||
|
||||
|
@ -186,6 +183,11 @@ export interface IUserDataSyncStoreService {
|
|||
resolveContent(resource: ServerResource, ref: string): Promise<string | null>;
|
||||
}
|
||||
|
||||
export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService');
|
||||
export interface IUserDataSyncStoreService extends IUserDataSyncStoreClient {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export const IUserDataSyncBackupStoreService = createDecorator<IUserDataSyncBackupStoreService>('IUserDataSyncBackupStoreService');
|
||||
export interface IUserDataSyncBackupStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, } from 'vs/base/common/lifecycle';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, ServerResource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest, IResourceRefHandle, HEADER_OPERATION_ID, HEADER_EXECUTION_ID, CONFIGURATION_SYNC_STORE_KEY, IAuthenticationProvider, IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IRequestService, asText, isSuccess as isSuccessContext, asJson } from 'vs/platform/request/common/request';
|
||||
import { joinPath, relativePath } from 'vs/base/common/resources';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
@ -125,9 +125,7 @@ export class UserDataSyncStoreManagementService extends AbstractUserDataSyncStor
|
|||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
|
||||
|
||||
_serviceBrand: any;
|
||||
export class UserDataSyncStoreClient extends Disposable implements IUserDataSyncStoreClient {
|
||||
|
||||
private readonly userDataSyncStoreUrl: URI | undefined;
|
||||
|
||||
|
@ -147,16 +145,16 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
|||
readonly onDidChangeDonotMakeRequestsUntil = this._onDidChangeDonotMakeRequestsUntil.event;
|
||||
|
||||
constructor(
|
||||
userDataSyncStoreUrl: URI | undefined,
|
||||
@IProductService productService: IProductService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
this.userDataSyncStoreUrl = this.userDataSyncStoreManagementService.userDataSyncStore ? joinPath(this.userDataSyncStoreManagementService.userDataSyncStore.url, 'v1') : undefined;
|
||||
this.userDataSyncStoreUrl = userDataSyncStoreUrl ? joinPath(userDataSyncStoreUrl, 'v1') : undefined;
|
||||
this.commonHeadersPromise = getServiceMachineId(environmentService, fileService, storageService)
|
||||
.then(uuid => {
|
||||
const headers: IHeaders = {
|
||||
|
@ -448,6 +446,23 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
|
|||
|
||||
}
|
||||
|
||||
export class UserDataSyncStoreService extends UserDataSyncStoreClient implements IUserDataSyncStoreService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncStoreManagementService userDataSyncStoreManagementService: IUserDataSyncStoreManagementService,
|
||||
@IProductService productService: IProductService,
|
||||
@IRequestService requestService: IRequestService,
|
||||
@IUserDataSyncLogService logService: IUserDataSyncLogService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
) {
|
||||
super(userDataSyncStoreManagementService.userDataSyncStore?.url, productService, requestService, logService, environmentService, fileService, storageService);
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestsSession {
|
||||
|
||||
private requests: string[] = [];
|
||||
|
|
|
@ -10,7 +10,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
|||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { getKeybindingsContentFromSyncContent, KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
suite('KeybindingsSync', () => {
|
||||
|
@ -70,8 +70,8 @@ suite('KeybindingsSync', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), '[]');
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), '');
|
||||
});
|
||||
|
||||
|
@ -95,8 +95,8 @@ suite('KeybindingsSync', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
|
@ -110,8 +110,8 @@ suite('KeybindingsSync', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), expectedContent);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), expectedContent);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), expectedContent);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), expectedContent);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedContent);
|
||||
});
|
||||
|
||||
|
@ -135,8 +135,8 @@ suite('KeybindingsSync', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
|
@ -159,8 +159,8 @@ suite('KeybindingsSync', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), content);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), content);
|
||||
assert.equal(getKeybindingsContentFromSyncContent(remoteUserData!.syncData!.content!, true), content);
|
||||
assert.equal((await fileService.readFile(keybindingsResource)).value.toString(), expectedLocalContent);
|
||||
});
|
||||
|
||||
|
@ -183,7 +183,7 @@ suite('KeybindingsSync', () => {
|
|||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
|
||||
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
|
||||
assert.equal(testObject.getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!), '[]');
|
||||
assert.equal(getKeybindingsContentFromSyncContent(lastSyncUserData!.syncData!.content!, true), '[]');
|
||||
});
|
||||
|
||||
test('test apply remote when keybindings file does not exist', async () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as assert from 'assert';
|
|||
import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, UserDataSyncError, UserDataSyncErrorCode, ISyncData, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SettingsSynchroniser, ISettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { SettingsSynchroniser, ISettingsSyncContent, parseSettingsSyncContent } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
@ -88,8 +88,8 @@ suite('SettingsSync - Auto', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal((await fileService.readFile(settingsResource)).value.toString(), '');
|
||||
});
|
||||
|
||||
|
@ -129,8 +129,8 @@ suite('SettingsSync - Auto', () => {
|
|||
|
||||
const lastSyncUserData = await testObject.getLastSyncUserData();
|
||||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(testObject.parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal(parseSettingsSyncContent(remoteUserData!.syncData!.content!)?.settings, content);
|
||||
assert.equal((await fileService.readFile(settingsResource)).value.toString(), content);
|
||||
});
|
||||
|
||||
|
@ -154,7 +154,7 @@ suite('SettingsSync - Auto', () => {
|
|||
const remoteUserData = await testObject.getRemoteUserData(null);
|
||||
assert.deepEqual(lastSyncUserData!.ref, remoteUserData.ref);
|
||||
assert.deepEqual(lastSyncUserData!.syncData, remoteUserData.syncData);
|
||||
assert.equal(testObject.parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
assert.equal(parseSettingsSyncContent(lastSyncUserData!.syncData!.content!)?.settings, '{}');
|
||||
});
|
||||
|
||||
test('sync for first time to the server', async () => {
|
||||
|
|
|
@ -28,12 +28,13 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { getAuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { AuthenticationSession } from 'vs/editor/common/modes';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class ViewContainerActivityAction extends ActivityAction {
|
||||
|
||||
|
@ -125,7 +126,8 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
|
|||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
) {
|
||||
super(action, { draggable: false, colors, icon: true }, themeService);
|
||||
}
|
||||
|
@ -178,10 +180,11 @@ export class AccountsActionViewItem extends ActivityActionViewItem {
|
|||
|
||||
const result = await Promise.all(allSessions);
|
||||
let menus: IAction[] = [];
|
||||
const authenticationSession = this.environmentService.options?.credentialsProvider ? await getAuthenticationSession(this.environmentService.options?.credentialsProvider, this.productService) : undefined;
|
||||
result.forEach(sessionInfo => {
|
||||
const providerDisplayName = this.authenticationService.getLabel(sessionInfo.providerId);
|
||||
Object.keys(sessionInfo.sessions).forEach(accountName => {
|
||||
const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === this.environmentService.options?.authenticationSessionId);
|
||||
const hasEmbedderAccountSession = sessionInfo.sessions[accountName].some(session => session.id === (authenticationSession?.id || this.environmentService.options?.authenticationSessionId));
|
||||
const manageExtensionsAction = new Action(`configureSessions${accountName}`, nls.localize('manageTrustedExtensions', "Manage Trusted Extensions"), '', true, _ => {
|
||||
return this.authenticationService.manageTrustedExtensionsForAccount(sessionInfo.providerId, accountName);
|
||||
});
|
||||
|
|
|
@ -50,6 +50,9 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil
|
|||
import { WebResourceIdentityService, IResourceIdentityService } from 'vs/platform/resource/common/resourceIdentityService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, INDEXEDDB_USERDATA_OBJECT_STORE } from 'vs/platform/files/browser/indexedDBFileSystemProvider';
|
||||
import { BrowserRequestService } from 'vs/workbench/services/request/browser/requestService';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
|
||||
class BrowserMain extends Disposable {
|
||||
|
||||
|
@ -180,7 +183,7 @@ class BrowserMain extends Disposable {
|
|||
await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath);
|
||||
|
||||
// Long running services (workspace, config, storage)
|
||||
const services = await Promise.all([
|
||||
const [configurationService, storageService] = await Promise.all([
|
||||
this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(service => {
|
||||
|
||||
// Workspace
|
||||
|
@ -201,7 +204,16 @@ class BrowserMain extends Disposable {
|
|||
})
|
||||
]);
|
||||
|
||||
return { serviceCollection, logService, storageService: services[1] };
|
||||
// Request Service
|
||||
const requestService = new BrowserRequestService(remoteAgentService, configurationService, logService);
|
||||
serviceCollection.set(IRequestService, requestService);
|
||||
|
||||
// initialize user data
|
||||
const userDataInitializationService = new UserDataInitializationService(environmentService, fileService, storageService, productService, requestService, logService);
|
||||
serviceCollection.set(IUserDataInitializationService, userDataInitializationService);
|
||||
await userDataInitializationService.initializeRequiredResources();
|
||||
|
||||
return { serviceCollection, logService, storageService };
|
||||
}
|
||||
|
||||
private async registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IFileService, remoteAgentService: IRemoteAgentService, logService: BufferLogService, logsPath: URI): Promise<void> {
|
||||
|
|
|
@ -16,9 +16,27 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
|||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { ICredentialsProvider } from 'vs/platform/credentials/common/credentials';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
|
||||
export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; }
|
||||
|
||||
export async function getAuthenticationSession(credentialsProvider: ICredentialsProvider, productService: IProductService): Promise<{ id: string, accessToken: string, providerId: string } | undefined> {
|
||||
const authenticationSessionValue = await credentialsProvider.getPassword(`${productService.urlProtocol}.login`, 'account');
|
||||
if (authenticationSessionValue) {
|
||||
const authenticationSession: { id: string, accessToken: string, providerId: string } = JSON.parse(authenticationSessionValue);
|
||||
if (authenticationSession
|
||||
&& isString(authenticationSession.id)
|
||||
&& isString(authenticationSession.accessToken)
|
||||
&& isString(authenticationSession.providerId)
|
||||
) {
|
||||
return authenticationSession;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const IAuthenticationService = createDecorator<IAuthenticationService>('IAuthenticationService');
|
||||
|
||||
export interface IAuthenticationService {
|
||||
|
|
|
@ -3,21 +3,11 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { ICredentialsProvider, ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
|
||||
export interface ICredentialsProvider {
|
||||
getPassword(service: string, account: string): Promise<string | null>;
|
||||
setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
|
||||
deletePassword(service: string, account: string): Promise<boolean>;
|
||||
|
||||
findPassword(service: string): Promise<string | null>;
|
||||
findCredentials(service: string): Promise<Array<{ account: string, password: string; }>>;
|
||||
}
|
||||
|
||||
export class BrowserCredentialsService implements ICredentialsService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
|
|
@ -24,6 +24,8 @@ import { FetchFileSystemProvider } from 'vs/workbench/services/extensions/browse
|
|||
import { Schemas } from 'vs/base/common/network';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
|
||||
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
|
||||
|
||||
|
@ -43,6 +45,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IConfigurationService private readonly _configService: IConfigurationService,
|
||||
@IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService,
|
||||
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
|
||||
@IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService,
|
||||
) {
|
||||
super(
|
||||
instantiationService,
|
||||
|
@ -56,7 +60,12 @@ export class ExtensionService extends AbstractExtensionService implements IExten
|
|||
|
||||
this._runningLocation = new Map<string, ExtensionRunningLocation>();
|
||||
|
||||
this._initialize();
|
||||
// Initialize extensions first and do it only after workbench is ready
|
||||
this._lifecycleService.when(LifecyclePhase.Ready).then(async () => {
|
||||
await this._userDataInitializationService.initializeExtensions(this._instantiationService);
|
||||
this._initialize();
|
||||
});
|
||||
|
||||
this._initFetchFileSystem();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,6 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|||
import { RequestChannelClient } from 'vs/platform/request/common/requestIpc';
|
||||
import { IRemoteAgentService, IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { RequestService } from 'vs/platform/request/browser/requestService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
|
||||
export class BrowserRequestService extends RequestService {
|
||||
|
||||
|
@ -44,5 +42,3 @@ export class BrowserRequestService extends RequestService {
|
|||
return connection.withChannel('request', channel => RequestChannelClient.request(channel, options, token));
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IRequestService, BrowserRequestService, true);
|
||||
|
|
170
src/vs/workbench/services/userData/browser/userDataInit.ts
Normal file
170
src/vs/workbench/services/userData/browser/userDataInit.ts
Normal file
|
@ -0,0 +1,170 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { AbstractInitializer } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { ExtensionsInitializer } from 'vs/platform/userDataSync/common/extensionsSync';
|
||||
import { GlobalStateInitializer } from 'vs/platform/userDataSync/common/globalStateSync';
|
||||
import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync';
|
||||
import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync';
|
||||
import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IRequestService } from 'vs/platform/request/common/request';
|
||||
import { CONFIGURATION_SYNC_STORE_KEY, IUserDataSyncStoreClient, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getAuthenticationSession } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
|
||||
export const IUserDataInitializationService = createDecorator<IUserDataInitializationService>('IUserDataInitializationService');
|
||||
export interface IUserDataInitializationService {
|
||||
_serviceBrand: any;
|
||||
|
||||
initializeRequiredResources(): Promise<void>;
|
||||
initializeOtherResources(): Promise<void>;
|
||||
initializeExtensions(instantiationService: IInstantiationService): Promise<void>;
|
||||
}
|
||||
|
||||
export class UserDataInitializationService implements IUserDataInitializationService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly initialized: SyncResource[] = [];
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
private _userDataSyncStoreClientPromise: Promise<IUserDataSyncStoreClient | undefined> | undefined;
|
||||
private createUserDataSyncStoreClient(): Promise<IUserDataSyncStoreClient | undefined> {
|
||||
if (!this._userDataSyncStoreClientPromise) {
|
||||
this._userDataSyncStoreClientPromise = (async (): Promise<IUserDataSyncStoreClient | undefined> => {
|
||||
if (!isWeb) {
|
||||
this.logService.trace(`Skipping initializing user data in desktop`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.environmentService.options?.enableSyncByDefault) {
|
||||
this.logService.trace(`Skipping initializing user data as sync is not enabled by default`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.storageService.isNew(StorageScope.GLOBAL)) {
|
||||
this.logService.trace(`Skipping initializing user data as application was opened before`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.storageService.isNew(StorageScope.WORKSPACE)) {
|
||||
this.logService.trace(`Skipping initializing user data as workspace was opened before`);
|
||||
return;
|
||||
}
|
||||
|
||||
const userDataSyncStore = this.productService[CONFIGURATION_SYNC_STORE_KEY];
|
||||
if (!userDataSyncStore) {
|
||||
this.logService.trace(`Skipping initializing user data as sync service is not provided`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.environmentService.options?.credentialsProvider) {
|
||||
this.logService.trace(`Skipping initializing user data as credentials provider is not provided`);
|
||||
return;
|
||||
}
|
||||
|
||||
let authenticationSession;
|
||||
try {
|
||||
authenticationSession = await getAuthenticationSession(this.environmentService.options.credentialsProvider, this.productService);
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
if (!authenticationSession) {
|
||||
this.logService.trace(`Skipping initializing user data as authentication session is not set`);
|
||||
return;
|
||||
}
|
||||
|
||||
const userDataSyncStoreClient = new UserDataSyncStoreClient(URI.parse(userDataSyncStore.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService);
|
||||
userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId);
|
||||
return userDataSyncStoreClient;
|
||||
})();
|
||||
}
|
||||
|
||||
return this._userDataSyncStoreClientPromise;
|
||||
}
|
||||
|
||||
async initializeRequiredResources(): Promise<void> {
|
||||
return this.initialize([SyncResource.Settings, SyncResource.GlobalState]);
|
||||
}
|
||||
|
||||
async initializeOtherResources(): Promise<void> {
|
||||
return this.initialize([SyncResource.Keybindings, SyncResource.Snippets]);
|
||||
}
|
||||
|
||||
async initializeExtensions(instantiationService: IInstantiationService): Promise<void> {
|
||||
return this.initialize([SyncResource.Extensions], instantiationService);
|
||||
}
|
||||
|
||||
private async initialize(syncResources: SyncResource[], instantiationService?: IInstantiationService): Promise<void> {
|
||||
const userDataSyncStoreClient = await this.createUserDataSyncStoreClient();
|
||||
if (!userDataSyncStoreClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(syncResources.map(async syncResource => {
|
||||
try {
|
||||
if (this.initialized.includes(syncResource)) {
|
||||
this.logService.info(`${getSyncAreaLabel(syncResource)} initialized already.`);
|
||||
return;
|
||||
}
|
||||
this.initialized.push(syncResource);
|
||||
this.logService.trace(`Initializing ${getSyncAreaLabel(syncResource)}`);
|
||||
const initializer = this.createSyncResourceInitializer(syncResource, instantiationService);
|
||||
const userData = await userDataSyncStoreClient.read(syncResource, null);
|
||||
await initializer.initialize(userData);
|
||||
this.logService.info(`Initialized ${getSyncAreaLabel(syncResource)}`);
|
||||
} catch (error) {
|
||||
this.logService.info(`Error while initializing ${getSyncAreaLabel(syncResource)}`);
|
||||
this.logService.error(error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private createSyncResourceInitializer(syncResource: SyncResource, instantiationService?: IInstantiationService): AbstractInitializer {
|
||||
switch (syncResource) {
|
||||
case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.environmentService, this.logService);
|
||||
case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.environmentService, this.logService);
|
||||
case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.environmentService, this.logService);
|
||||
case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.environmentService, this.logService);
|
||||
case SyncResource.Extensions:
|
||||
if (!instantiationService) {
|
||||
throw new Error('Instantiation Service is required to initialize extension');
|
||||
}
|
||||
return instantiationService.createInstance(ExtensionsInitializer);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InitializeOtherResourcesContribution implements IWorkbenchContribution {
|
||||
constructor(@IUserDataInitializationService userDataInitializeService: IUserDataInitializationService) {
|
||||
userDataInitializeService.initializeOtherResources();
|
||||
}
|
||||
}
|
||||
|
||||
if (isWeb) {
|
||||
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
|
||||
workbenchRegistry.registerWorkbenchContribution(InitializeOtherResourcesContribution, LifecyclePhase.Eventually);
|
||||
}
|
|
@ -11,7 +11,7 @@ import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/edi
|
|||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { flatten, equals } from 'vs/base/common/arrays';
|
||||
import { getAuthenticationProviderActivationEvent, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { getAuthenticationProviderActivationEvent, getAuthenticationSession, IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
|
||||
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
|
@ -153,8 +153,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
|
|||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
if (this.currentSessionId === undefined && this.useWorkbenchSessionId && this.environmentService.options?.authenticationSessionId) {
|
||||
this.currentSessionId = this.environmentService.options.authenticationSessionId;
|
||||
const authenticationSession = this.environmentService.options?.credentialsProvider ? await getAuthenticationSession(this.environmentService.options?.credentialsProvider, this.productService) : undefined;
|
||||
if (this.currentSessionId === undefined && this.useWorkbenchSessionId && (authenticationSession?.id || this.environmentService.options?.authenticationSessionId)) {
|
||||
this.currentSessionId = authenticationSession?.id || this.environmentService.options?.authenticationSessionId;
|
||||
this.useWorkbenchSessionId = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,12 +69,14 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
|
|||
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
|
||||
import { ITimerService } from 'vs/workbench/services/timer/browser/timerService';
|
||||
import { TimerService } from 'vs/workbench/services/timer/electron-browser/timerService';
|
||||
import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
|
||||
registerSingleton(ICredentialsService, KeytarCredentialsService, true);
|
||||
registerSingleton(IUserDataSyncStoreManagementService, UserDataSyncStoreManagementService);
|
||||
registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService);
|
||||
registerSingleton(ITunnelService, TunnelService);
|
||||
registerSingleton(ITimerService, TimerService);
|
||||
registerSingleton(IUserDataInitializationService, UserDataInitializationService);
|
||||
|
||||
//#endregion
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import { main } from 'vs/workbench/browser/web.main';
|
|||
import { UriComponents, URI } from 'vs/base/common/uri';
|
||||
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, FileChangeType } from 'vs/platform/files/common/files';
|
||||
import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory';
|
||||
import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService';
|
||||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
|
@ -19,6 +18,7 @@ import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/brows
|
|||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IProductConfiguration } from 'vs/platform/product/common/productService';
|
||||
import { mark } from 'vs/base/common/performance';
|
||||
import { ICredentialsProvider } from 'vs/platform/credentials/common/credentials';
|
||||
|
||||
interface IResourceUriProvider {
|
||||
(uri: URI): URI;
|
||||
|
|
|
@ -46,7 +46,6 @@ import 'vs/workbench/services/workspaces/browser/workspaceEditingService';
|
|||
import 'vs/workbench/services/dialogs/browser/dialogService';
|
||||
import 'vs/workbench/services/dialogs/browser/fileDialogService';
|
||||
import 'vs/workbench/services/host/browser/browserHostService';
|
||||
import 'vs/workbench/services/request/browser/requestService';
|
||||
import 'vs/workbench/services/lifecycle/browser/lifecycleService';
|
||||
import 'vs/workbench/services/clipboard/browser/clipboardService';
|
||||
import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService';
|
||||
|
|
Loading…
Reference in a new issue