From 9c4d6d295779cf538bceb2936cf7e705d1144fc9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Apr 2023 20:56:39 +0200 Subject: [PATCH] support loading profile in web (#180331) * support loading profile in web - embedder api to pass profile and contents - initialise the profile * fix typo --- .../common/abstractSynchronizer.ts | 4 +- .../userDataSync/common/userDataSync.ts | 2 +- src/vs/workbench/browser/web.api.ts | 5 + src/vs/workbench/browser/web.main.ts | 39 +- .../services/userData/browser/userDataInit.ts | 406 +---------------- .../browser/extensionsResource.ts | 69 ++- .../browser/globalStateResource.ts | 18 +- .../browser/keybindingsResource.ts | 21 +- .../browser/settingsResource.ts | 21 +- .../browser/snippetsResource.ts | 20 +- .../userDataProfile/browser/tasksResource.ts | 21 +- .../browser/userDataProfileInit.ts | 156 +++++++ .../userDataProfile/common/userDataProfile.ts | 4 + .../userDataSync/browser/userDataSyncInit.ts | 418 ++++++++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 3 +- 15 files changed, 797 insertions(+), 410 deletions(-) create mode 100644 src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts create mode 100644 src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 9451928cf66..c3569093b33 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -26,7 +26,7 @@ import { getServiceMachineId } from 'vs/platform/externalServices/common/service import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataInitializer, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { Change, getLastSyncResourceUri, IRemoteUserData, IResourcePreview as IBaseResourcePreview, ISyncData, IUserDataSyncResourcePreview as IBaseSyncResourcePreview, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncBackupStoreService, IUserDataSyncConfiguration, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, IUserDataSyncUtilService, MergeState, PREVIEW_DIR_NAME, SyncResource, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_CONFIGURATION_SCOPE, USER_DATA_SYNC_SCHEME, IUserDataResourceManifest, getPathSegments, IUserDataSyncResourceConflicts, IUserDataSyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; type IncompatibleSyncSourceClassification = { @@ -915,7 +915,7 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni } -export abstract class AbstractInitializer implements IUserDataInitializer { +export abstract class AbstractInitializer implements IUserDataSyncResourceInitializer { protected readonly extUri: IExtUri; private readonly lastSyncResource: URI; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 2dda9832803..72f7ed2e60e 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -424,7 +424,7 @@ export interface IUserDataSyncResourceError extends IUserDataSyncResource { readonly error: UserDataSyncError; } -export interface IUserDataInitializer { +export interface IUserDataSyncResourceInitializer { initialize(userData: IUserData): Promise; } diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index b99bc694fb1..dc490bd6a9e 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -259,6 +259,11 @@ export interface IWorkbenchConstructionOptions { //#region Profile options + /** + * Profile to use for the workbench. + */ + readonly profile?: { readonly name: string; readonly contents?: string | UriComponents }; + /** * URI of the profile to preview. */ diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 9140b621766..d5e709382ee 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -45,7 +45,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IndexedDBFileSystemProviderErrorDataClassification, IndexedDBFileSystemProvider, IndexedDBFileSystemProviderErrorData } 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'; +import { IUserDataInitializationService, IUserDataInitializer, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { UserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSync'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -73,7 +73,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel'; import { dirname, joinPath } from 'vs/base/common/resources'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { IRemoteExplorerService, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { DisposableTunnel, TunnelProtocol } from 'vs/platform/tunnel/common/tunnel'; @@ -86,6 +86,8 @@ import { windowLogId } from 'vs/workbench/services/log/common/logConstants'; import { LogService } from 'vs/platform/log/common/logService'; import { VSBuffer } from 'vs/base/common/buffer'; import { IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; +import { UserDataProfileInitializer } from 'vs/workbench/services/userDataProfile/browser/userDataProfileInit'; +import { UserDataSyncInitializer } from 'vs/workbench/services/userDataSync/browser/userDataSyncInit'; export class BrowserMain extends Disposable { @@ -281,6 +283,10 @@ export class BrowserMain extends Disposable { const loggerService = new FileLoggerService(logLevel, logsPath, fileService); serviceCollection.set(ILoggerService, loggerService); + // Register File System Providers depending on IndexedDB support + // Register them early because they are needed for the profiles initialization + await this.registerIndexedDBFileSystemProviders(environmentService, fileService, bufferLogger, logService, loggerService, logsPath); + // URI Identity const uriIdentityService = new UriIdentityService(fileService); serviceCollection.set(IUriIdentityService, uriIdentityService); @@ -289,15 +295,14 @@ export class BrowserMain extends Disposable { const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - const currentProfile = userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile; + const currentProfile = await this.getCurrentProfile(workspace, userDataProfilesService, environmentService); const userDataProfileService = new UserDataProfileService(currentProfile, userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); // Remote Agent const remoteAgentService = this._register(new RemoteAgentService(this.configuration.webSocketFactory, userDataProfileService, environmentService, productService, remoteAuthorityResolverService, signService, logService)); serviceCollection.set(IRemoteAgentService, remoteAgentService); - - await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, bufferLogger, logService, loggerService, logsPath); + this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); // Long running services (workspace, config, storage) const [configurationService, storageService] = await Promise.all([ @@ -365,7 +370,12 @@ export class BrowserMain extends Disposable { serviceCollection.set(ICredentialsService, credentialsService); // Userdata Initialize Service - const userDataInitializationService = new UserDataInitializationService(environmentService, credentialsService, userDataSyncStoreManagementService, fileService, userDataProfilesService, storageService, productService, requestService, logService, uriIdentityService); + const userDataInitializers: IUserDataInitializer[] = []; + userDataInitializers.push(new UserDataSyncInitializer(environmentService, credentialsService, userDataSyncStoreManagementService, fileService, userDataProfilesService, storageService, productService, requestService, logService, uriIdentityService)); + if (environmentService.options.profile) { + userDataInitializers.push(new UserDataProfileInitializer(environmentService, fileService, userDataProfileService, storageService, logService, uriIdentityService, requestService)); + } + const userDataInitializationService = new UserDataInitializationService(userDataInitializers); serviceCollection.set(IUserDataInitializationService, userDataInitializationService); try { @@ -396,8 +406,7 @@ export class BrowserMain extends Disposable { } } - private async registerFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IWorkbenchFileService, remoteAgentService: IRemoteAgentService, bufferLogger: BufferLogger, logService: ILogService, loggerService: ILoggerService, logsPath: URI): Promise { - + private async registerIndexedDBFileSystemProviders(environmentService: IWorkbenchEnvironmentService, fileService: IWorkbenchFileService, bufferLogger: BufferLogger, logService: ILogService, loggerService: ILoggerService, logsPath: URI): Promise { // IndexedDB is used for logging and user data let indexedDB: IndexedDB | undefined; const userDataStore = 'vscode-userdata-store'; @@ -435,9 +444,6 @@ export class BrowserMain extends Disposable { } fileService.registerProvider(Schemas.vscodeUserData, userDataProvider); - // Remote file system - this._register(RemoteFileSystemProviderClient.register(remoteAgentService, fileService, logService)); - // Local file access (if supported by browser) if (WebFileSystemAccess.supported(window)) { fileService.registerProvider(Schemas.file, new HTMLFileSystemProvider(indexedDB, handlesStore, logService)); @@ -538,6 +544,17 @@ export class BrowserMain extends Disposable { } } + private async getCurrentProfile(workspace: IAnyWorkspaceIdentifier, userDataProfilesService: BrowserUserDataProfilesService, environmentService: IBrowserWorkbenchEnvironmentService): Promise { + if (environmentService.options?.profile) { + const profile = userDataProfilesService.profiles.find(p => p.name === environmentService.options?.profile?.name); + if (profile) { + return profile; + } + return userDataProfilesService.createNamedProfile(environmentService.options?.profile?.name, undefined, workspace); + } + return userDataProfilesService.getProfileForWorkspace(workspace) ?? userDataProfilesService.defaultProfile; + } + private resolveWorkspace(): IAnyWorkspaceIdentifier { let workspace: IWorkspace | undefined = undefined; if (this.configuration.workspaceProvider) { diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index d6158b36af1..3f0b96c7d77 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -3,46 +3,15 @@ * 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 { AbstractExtensionsInitializer, IExtensionsInitializerPreviewResult } from 'vs/platform/userDataSync/common/extensionsSync'; -import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } 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 { 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 { IRemoteUserData, IUserData, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } 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/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { Barrier, Promises } from 'vs/base/common/async'; -import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { mark } from 'vs/base/common/performance'; -import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { isEqual } from 'vs/base/common/resources'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; - -export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); -export interface IUserDataInitializationService { - _serviceBrand: any; +export interface IUserDataInitializer { requiresInitialization(): Promise; whenInitializationFinished(): Promise; initializeRequiredResources(): Promise; @@ -50,385 +19,44 @@ export interface IUserDataInitializationService { initializeOtherResources(instantiationService: IInstantiationService): Promise; } +export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); +export interface IUserDataInitializationService extends IUserDataInitializer { + _serviceBrand: any; +} + export class UserDataInitializationService implements IUserDataInitializationService { _serviceBrand: any; - private readonly initialized: SyncResource[] = []; - private readonly initializationFinished = new Barrier(); - private globalStateUserData: IUserData | null = null; - - constructor( - @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, - @ICredentialsService private readonly credentialsService: ICredentialsService, - @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, - @IFileService private readonly fileService: IFileService, - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IStorageService private readonly storageService: IStorageService, - @IProductService private readonly productService: IProductService, - @IRequestService private readonly requestService: IRequestService, - @ILogService private readonly logService: ILogService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - ) { - this.createUserDataSyncStoreClient().then(userDataSyncStoreClient => { - if (!userDataSyncStoreClient) { - this.initializationFinished.open(); - } - }); - } - - private _userDataSyncStoreClientPromise: Promise | undefined; - private createUserDataSyncStoreClient(): Promise { - if (!this._userDataSyncStoreClientPromise) { - this._userDataSyncStoreClientPromise = (async (): Promise => { - try { - if (!isWeb) { - this.logService.trace(`Skipping initializing user data in desktop`); - return; - } - - if (!this.storageService.isNew(StorageScope.APPLICATION)) { - 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; - } - - let authenticationSession; - try { - authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); - } catch (error) { - this.logService.error(error); - } - if (!authenticationSession) { - this.logService.trace(`Skipping initializing user data as authentication session is not set`); - return; - } - - await this.initializeUserDataSyncStore(authenticationSession); - - const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; - if (!userDataSyncStore) { - this.logService.trace(`Skipping initializing user data as sync service is not provided`); - return; - } - - const userDataSyncStoreClient = new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); - userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); - - const manifest = await userDataSyncStoreClient.manifest(null); - if (manifest === null) { - userDataSyncStoreClient.dispose(); - this.logService.trace(`Skipping initializing user data as there is no data`); - return; - } - - this.logService.info(`Using settings sync service ${userDataSyncStore.url.toString()} for initialization`); - return userDataSyncStoreClient; - - } catch (error) { - this.logService.error(error); - return; - } - })(); - } - - return this._userDataSyncStoreClientPromise; - } - - private async initializeUserDataSyncStore(authenticationSession: AuthenticationSessionInfo): Promise { - const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; - if (!userDataSyncStore?.canSwitch) { - return; - } - - const disposables = new DisposableStore(); - try { - const userDataSyncStoreClient = disposables.add(new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService)); - userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); - - // Cache global state data for global state initialization - this.globalStateUserData = await userDataSyncStoreClient.readResource(SyncResource.GlobalState, null); - - if (this.globalStateUserData) { - const userDataSyncStoreType = new UserDataSyncStoreTypeSynchronizer(userDataSyncStoreClient, this.storageService, this.environmentService, this.fileService, this.logService).getSyncStoreType(this.globalStateUserData); - if (userDataSyncStoreType) { - await this.userDataSyncStoreManagementService.switch(userDataSyncStoreType); - - // Unset cached global state data if urls are changed - if (!isEqual(userDataSyncStore.url, this.userDataSyncStoreManagementService.userDataSyncStore?.url)) { - this.logService.info('Switched settings sync store'); - this.globalStateUserData = null; - } - } - } - } finally { - disposables.dispose(); - } + constructor(private readonly initializers: IUserDataInitializer[] = []) { } async whenInitializationFinished(): Promise { - await this.initializationFinished.wait(); + await Promise.all(this.initializers.map(initializer => initializer.whenInitializationFinished())); } async requiresInitialization(): Promise { - this.logService.trace(`UserDataInitializationService#requiresInitialization`); - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - return !!userDataSyncStoreClient; + return (await Promise.all(this.initializers.map(initializer => initializer.requiresInitialization()))).some(result => result); } async initializeRequiredResources(): Promise { - this.logService.trace(`UserDataInitializationService#initializeRequiredResources`); - return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeRequiredResources())); + } } async initializeOtherResources(instantiationService: IInstantiationService): Promise { - try { - this.logService.trace(`UserDataInitializationService#initializeOtherResources`); - await Promise.allSettled([this.initialize([SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Tasks]), this.initializeExtensions(instantiationService)]); - } finally { - this.initializationFinished.open(); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeOtherResources(instantiationService))); } } - private async initializeExtensions(instantiationService: IInstantiationService): Promise { - try { - await Promise.all([this.initializeInstalledExtensions(instantiationService), this.initializeNewExtensions(instantiationService)]); - } finally { - this.initialized.push(SyncResource.Extensions); - } - } - - private initializeInstalledExtensionsPromise: Promise | undefined; async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { - if (!this.initializeInstalledExtensionsPromise) { - this.initializeInstalledExtensionsPromise = (async () => { - this.logService.trace(`UserDataInitializationService#initializeInstalledExtensions`); - const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); - if (extensionsPreviewInitializer) { - await instantiationService.createInstance(InstalledExtensionsInitializer, extensionsPreviewInitializer).initialize(); - } - })(); - } - return this.initializeInstalledExtensionsPromise; - } - - private initializeNewExtensionsPromise: Promise | undefined; - private async initializeNewExtensions(instantiationService: IInstantiationService): Promise { - if (!this.initializeNewExtensionsPromise) { - this.initializeNewExtensionsPromise = (async () => { - this.logService.trace(`UserDataInitializationService#initializeNewExtensions`); - const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); - if (extensionsPreviewInitializer) { - await instantiationService.createInstance(NewExtensionsInitializer, extensionsPreviewInitializer).initialize(); - } - })(); - } - return this.initializeNewExtensionsPromise; - } - - private extensionsPreviewInitializerPromise: Promise | undefined; - private getExtensionsPreviewInitializer(instantiationService: IInstantiationService): Promise { - if (!this.extensionsPreviewInitializerPromise) { - this.extensionsPreviewInitializerPromise = (async () => { - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - if (!userDataSyncStoreClient) { - return null; - } - const userData = await userDataSyncStoreClient.readResource(SyncResource.Extensions, null); - return instantiationService.createInstance(ExtensionsPreviewInitializer, userData); - })(); - } - return this.extensionsPreviewInitializerPromise; - } - - private async initialize(syncResources: SyncResource[]): Promise { - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - if (!userDataSyncStoreClient) { - return; - } - - await Promises.settled(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); - const userData = await userDataSyncStoreClient.readResource(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : 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): IUserDataInitializer { - switch (syncResource) { - case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); - case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); - case SyncResource.Tasks: return new TasksInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); - case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); - case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - } - throw new Error(`Cannot create initializer for ${syncResource}`); - } - -} - -class ExtensionsPreviewInitializer extends AbstractExtensionsInitializer { - - private previewPromise: Promise | undefined; - private preview: IExtensionsInitializerPreviewResult | null = null; - - constructor( - private readonly extensionsData: IUserData, - @IExtensionManagementService extensionManagementService: IExtensionManagementService, - @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, - @IFileService fileService: IFileService, - @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, - @IEnvironmentService environmentService: IEnvironmentService, - @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IStorageService storageService: IStorageService, - @IUriIdentityService uriIdentityService: IUriIdentityService, - ) { - super(extensionManagementService, ignoredExtensionsManagementService, fileService, userDataProfilesService, environmentService, logService, storageService, uriIdentityService); - } - - getPreview(): Promise { - if (!this.previewPromise) { - this.previewPromise = super.initialize(this.extensionsData).then(() => this.preview); - } - return this.previewPromise; - } - - override initialize(): Promise { - throw new Error('should not be called directly'); - } - - protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { - const remoteExtensions = await this.parseExtensions(remoteUserData); - if (!remoteExtensions) { - this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); - return; - } - const installedExtensions = await this.extensionManagementService.getInstalled(); - this.preview = this.generatePreview(remoteExtensions, installedExtensions); - } -} - -class InstalledExtensionsInitializer implements IUserDataInitializer { - - constructor( - private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, - @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - ) { - } - - async initialize(): Promise { - const preview = await this.extensionsPreviewInitializer.getPreview(); - if (!preview) { - return; - } - - // 1. Initialise already installed extensions state - for (const installedExtension of preview.installedExtensions) { - const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); - if (syncExtension?.state) { - const extensionState = this.extensionStorageService.getExtensionState(installedExtension, true) || {}; - Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); - this.extensionStorageService.setExtensionState(installedExtension, extensionState, true); - } - } - - // 2. Initialise extensions enablement - if (preview.disabledExtensions.length) { - for (const identifier of preview.disabledExtensions) { - this.logService.trace(`Disabling extension...`, identifier.id); - await this.extensionEnablementService.disableExtension(identifier); - this.logService.info(`Disabling extension`, identifier.id); - } - } - } -} - -class NewExtensionsInitializer implements IUserDataInitializer { - - constructor( - private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, - @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - ) { - } - - async initialize(): Promise { - const preview = await this.extensionsPreviewInitializer.getPreview(); - if (!preview) { - return; - } - - const newlyEnabledExtensions: ILocalExtension[] = []; - const targetPlatform = await this.extensionManagementService.getTargetPlatform(); - const galleryExtensions = await this.galleryService.getExtensions(preview.newExtensions, { targetPlatform, compatible: true }, CancellationToken.None); - for (const galleryExtension of galleryExtensions) { - try { - const extensionToSync = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, galleryExtension.identifier)); - if (!extensionToSync) { - continue; - } - if (extensionToSync.state) { - this.extensionStorageService.setExtensionState(galleryExtension, extensionToSync.state, true); - } - this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { - isMachineScoped: false, /* set isMachineScoped to prevent install and sync dialog in web */ - donotIncludePackAndDependencies: true, - installGivenVersion: !!extensionToSync.version, - installPreReleaseVersion: extensionToSync.preRelease - }); - if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { - newlyEnabledExtensions.push(local); - } - this.logService.info(`Installed extension.`, galleryExtension.identifier.id); - } catch (error) { - this.logService.error(error); - } - } - - const canEnabledExtensions = newlyEnabledExtensions.filter(e => this.extensionService.canAddExtension(toExtensionDescription(e))); - if (!(await this.areExtensionsRunning(canEnabledExtensions))) { - await new Promise((c, e) => { - const disposable = this.extensionService.onDidChangeExtensions(async () => { - try { - if (await this.areExtensionsRunning(canEnabledExtensions)) { - disposable.dispose(); - c(); - } - } catch (error) { - e(error); - } - }); - }); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeInstalledExtensions(instantiationService))); } } - private async areExtensionsRunning(extensions: ILocalExtension[]): Promise { - await this.extensionService.whenInstalledExtensionsRegistered(); - const runningExtensions = this.extensionService.extensions; - return extensions.every(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier))); - } } class InitializeOtherResourcesContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts index 62dfa467698..e8fa77e0839 100644 --- a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts @@ -16,7 +16,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface IProfileExtension { identifier: IExtensionIdentifier; @@ -26,6 +26,73 @@ interface IProfileExtension { version?: string; } +export class ExtensionsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const profileExtensions: IProfileExtension[] = JSON.parse(content); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.userDataProfileService.currentProfile.extensionsResource); + const extensionsToEnableOrDisable: { extension: IExtensionIdentifier; enable: boolean }[] = []; + const extensionsToInstall: IProfileExtension[] = []; + for (const e of profileExtensions) { + const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); + if (!installedExtension || (!installedExtension.isBuiltin && installedExtension.preRelease !== e.preRelease)) { + extensionsToInstall.push(e); + } + if (isDisabled !== !!e.disabled) { + extensionsToEnableOrDisable.push({ extension: e.identifier, enable: !e.disabled }); + } + } + const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => !extension.isBuiltin && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); + for (const { extension, enable } of extensionsToEnableOrDisable) { + if (enable) { + this.logService.trace(`Initializing Profile: Enabling extension...`, extension.id); + await this.extensionEnablementService.enableExtension(extension); + this.logService.info(`Initializing Profile: Enabled extension...`, extension.id); + } else { + this.logService.trace(`Initializing Profile: Disabling extension...`, extension.id); + await this.extensionEnablementService.disableExtension(extension); + this.logService.info(`Initializing Profile: Disabled extension...`, extension.id); + } + } + if (extensionsToInstall.length) { + const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, version: e.version, hasPreRelease: e.version ? undefined : e.preRelease })), CancellationToken.None); + await Promise.all(extensionsToInstall.map(async e => { + const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); + if (!extension) { + return; + } + if (await this.extensionManagementService.canInstall(extension)) { + this.logService.trace(`Initializing Profile: Installing extension...`, extension.identifier.id, extension.version); + await this.extensionManagementService.installFromGallery(extension, { + isMachineScoped: false,/* set isMachineScoped value to prevent install and sync dialog in web */ + donotIncludePackAndDependencies: true, + installGivenVersion: !!e.version, + installPreReleaseVersion: e.preRelease, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true } + }); + this.logService.info(`Initializing Profile: Installed extension...`, extension.identifier.id, extension.version); + } else { + this.logService.info(`Initializing Profile: Skipped installing extension because it cannot be installed.`, extension.identifier.id); + } + })); + } + if (extensionsToUninstall.length) { + await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); + } + } +} + export class ExtensionsResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts index cdfc7a75daa..45e14547ba0 100644 --- a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts @@ -13,12 +13,28 @@ import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataPro import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface IGlobalState { storage: IStringDictionary; } +export class GlobalStateResourceInitializer implements IProfileResourceInitializer { + + constructor(@IStorageService private readonly storageService: IStorageService) { + } + + async initialize(content: string): Promise { + const globalState: IGlobalState = JSON.parse(content); + const storageKeys = Object.keys(globalState.storage); + if (storageKeys.length) { + for (const key of storageKeys) { + this.storageService.store(key, globalState.storage[key], StorageScope.PROFILE, StorageTarget.USER); + } + } + } +} + export class GlobalStateResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts index 8ff33f900ce..3c3f6b9be6a 100644 --- a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts @@ -6,7 +6,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { platform, Platform } from 'vs/base/common/platform'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -19,6 +19,25 @@ interface IKeybindingsResourceContent { keybindings: string | null; } +export class KeybindingsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const keybindingsContent: IKeybindingsResourceContent = JSON.parse(content); + if (keybindingsContent.keybindings === null) { + this.logService.info(`Initializing Profile: No keybindings to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(keybindingsContent.keybindings)); + } +} + export class KeybindingsResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts index 8f03f912918..f0d212d138b 100644 --- a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts @@ -8,7 +8,7 @@ import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platf import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; @@ -21,6 +21,25 @@ interface ISettingsContent { settings: string | null; } +export class SettingsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const settingsContent: ISettingsContent = JSON.parse(content); + if (settingsContent.settings === null) { + this.logService.info(`Initializing Profile: No settings to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString(settingsContent.settings)); + } +} + export class SettingsResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts index 3a154a7bf7e..82de3f6e6ca 100644 --- a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts @@ -14,12 +14,30 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface ISnippetsContent { snippets: IStringDictionary; } +export class SnippetsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + } + + async initialize(content: string): Promise { + const snippetsContent: ISnippetsContent = JSON.parse(content); + for (const key in snippetsContent.snippets) { + const resource = this.uriIdentityService.extUri.joinPath(this.userDataProfileService.currentProfile.snippetsHome, key); + await this.fileService.writeFile(resource, VSBuffer.fromString(snippetsContent.snippets[key])); + } + } +} + export class SnippetsResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts index 91fbc7d3310..2fc82df17e8 100644 --- a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts +++ b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts @@ -11,12 +11,31 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; -import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; interface ITasksResourceContent { tasks: string | null; } +export class TasksResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const tasksContent: ITasksResourceContent = JSON.parse(content); + if (!tasksContent.tasks) { + this.logService.info(`Initializing Profile: No tasks to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.tasksResource, VSBuffer.fromString(tasksContent.tasks)); + } +} + export class TasksResource implements IProfileResource { constructor( diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts new file mode 100644 index 00000000000..0c05c284536 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Barrier, Promises } from 'vs/base/common/async'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit'; +import { IProfileResourceInitializer, IUserDataProfileService, IUserDataProfileTemplate, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { SettingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/settingsResource'; +import { GlobalStateResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/globalStateResource'; +import { KeybindingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/keybindingsResource'; +import { TasksResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/tasksResource'; +import { SnippetsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/snippetsResource'; +import { ExtensionsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/extensionsResource'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { isString } from 'vs/base/common/types'; +import { IRequestService, asJson } from 'vs/platform/request/common/request'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; + +export class UserDataProfileInitializer implements IUserDataInitializer { + + _serviceBrand: any; + + private readonly initialized: ProfileResourceType[] = []; + private readonly initializationFinished = new Barrier(); + + constructor( + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IRequestService private readonly requestService: IRequestService, + ) { + } + + async whenInitializationFinished(): Promise { + await this.initializationFinished.wait(); + } + + async requiresInitialization(): Promise { + if (!this.environmentService.options?.profile?.contents) { + return false; + } + if (!this.storageService.isNew(StorageScope.PROFILE)) { + return false; + } + return true; + } + + async initializeRequiredResources(): Promise { + this.logService.trace(`UserDataProfileInitializer#initializeRequiredResources`); + const promises = []; + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.settings) { + promises.push(this.initialize(new SettingsResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.settings, ProfileResourceType.Settings)); + } + if (profileTemplate?.globalState) { + promises.push(this.initialize(new GlobalStateResourceInitializer(this.storageService), profileTemplate.globalState, ProfileResourceType.GlobalState)); + } + await Promise.all(promises); + } + + async initializeOtherResources(instantiationService: IInstantiationService): Promise { + try { + this.logService.trace(`UserDataProfileInitializer#initializeOtherResources`); + const promises = []; + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.keybindings) { + promises.push(this.initialize(new KeybindingsResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.keybindings, ProfileResourceType.Keybindings)); + } + if (profileTemplate?.tasks) { + promises.push(this.initialize(new TasksResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.tasks, ProfileResourceType.Tasks)); + } + if (profileTemplate?.snippets) { + promises.push(this.initialize(new SnippetsResourceInitializer(this.userDataProfileService, this.fileService, this.uriIdentityService), profileTemplate.snippets, ProfileResourceType.Snippets)); + } + promises.push(this.initializeInstalledExtensions(instantiationService)); + await Promises.settled(promises); + } finally { + this.initializationFinished.open(); + } + } + + private initializeInstalledExtensionsPromise: Promise | undefined; + async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeInstalledExtensionsPromise) { + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.extensions) { + this.initializeInstalledExtensionsPromise = this.initialize(instantiationService.createInstance(ExtensionsResourceInitializer), profileTemplate.extensions, ProfileResourceType.Extensions); + } else { + this.initializeInstalledExtensionsPromise = Promise.resolve(); + } + + } + return this.initializeInstalledExtensionsPromise; + } + + private profileTemplatePromise: Promise | undefined; + private getProfileTemplate(): Promise { + if (!this.profileTemplatePromise) { + this.profileTemplatePromise = this.doGetProfileTemplate(); + } + return this.profileTemplatePromise; + } + + private async doGetProfileTemplate(): Promise { + if (!this.environmentService.options?.profile?.contents) { + return null; + } + if (isString(this.environmentService.options.profile.contents)) { + try { + return JSON.parse(this.environmentService.options.profile.contents); + } catch (error) { + this.logService.error(error); + return null; + } + } + try { + const url = URI.revive(this.environmentService.options.profile.contents).toString(true); + const context = await this.requestService.request({ type: 'GET', url }, CancellationToken.None); + if (context.res.statusCode === 200) { + return await asJson(context); + } else { + this.logService.warn(`UserDataProfileInitializer: Failed to get profile from URL: ${url}. Status code: ${context.res.statusCode}.`); + } + } catch (error) { + this.logService.error(error); + } + return null; + } + + private async initialize(initializer: IProfileResourceInitializer, content: string, profileResource: ProfileResourceType): Promise { + try { + if (this.initialized.includes(profileResource)) { + this.logService.info(`UserDataProfileInitializer: ${profileResource} initialized already.`); + return; + } + this.initialized.push(profileResource); + this.logService.trace(`UserDataProfileInitializer: Initializing ${profileResource}`); + await initializer.initialize(content); + this.logService.info(`UserDataProfileInitializer: Initialized ${profileResource}`); + } catch (error) { + this.logService.info(`UserDataProfileInitializer: Error while initializing ${profileResource}`); + this.logService.error(error); + } + } + +} diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index 077c2859eb8..e21e7efd690 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -101,6 +101,10 @@ export const enum ProfileResourceType { GlobalState = 'globalState', } +export interface IProfileResourceInitializer { + initialize(content: string): Promise; +} + export interface IProfileResource { getContent(profile: IUserDataProfile): Promise; apply(content: string, profile: IUserDataProfile): Promise; diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts new file mode 100644 index 00000000000..7629a9a2688 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts @@ -0,0 +1,418 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { AbstractExtensionsInitializer, IExtensionsInitializerPreviewResult } from 'vs/platform/userDataSync/common/extensionsSync'; +import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } 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 { IFileService } from 'vs/platform/files/common/files'; +import { 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 { IRemoteUserData, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLogService, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { isWeb } from 'vs/base/common/platform'; +import { Barrier, Promises } from 'vs/base/common/async'; +import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit'; + +export class UserDataSyncInitializer implements IUserDataInitializer { + + _serviceBrand: any; + + private readonly initialized: SyncResource[] = []; + private readonly initializationFinished = new Barrier(); + private globalStateUserData: IUserData | null = null; + + constructor( + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, + @IFileService private readonly fileService: IFileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + @ILogService private readonly logService: ILogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + this.createUserDataSyncStoreClient().then(userDataSyncStoreClient => { + if (!userDataSyncStoreClient) { + this.initializationFinished.open(); + } + }); + } + + private _userDataSyncStoreClientPromise: Promise | undefined; + private createUserDataSyncStoreClient(): Promise { + if (!this._userDataSyncStoreClientPromise) { + this._userDataSyncStoreClientPromise = (async (): Promise => { + try { + if (!isWeb) { + this.logService.trace(`Skipping initializing user data in desktop`); + return; + } + + if (!this.storageService.isNew(StorageScope.APPLICATION)) { + 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; + } + + let authenticationSession; + try { + authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); + } catch (error) { + this.logService.error(error); + } + if (!authenticationSession) { + this.logService.trace(`Skipping initializing user data as authentication session is not set`); + return; + } + + await this.initializeUserDataSyncStore(authenticationSession); + + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore) { + this.logService.trace(`Skipping initializing user data as sync service is not provided`); + return; + } + + const userDataSyncStoreClient = new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + + const manifest = await userDataSyncStoreClient.manifest(null); + if (manifest === null) { + userDataSyncStoreClient.dispose(); + this.logService.trace(`Skipping initializing user data as there is no data`); + return; + } + + this.logService.info(`Using settings sync service ${userDataSyncStore.url.toString()} for initialization`); + return userDataSyncStoreClient; + + } catch (error) { + this.logService.error(error); + return; + } + })(); + } + + return this._userDataSyncStoreClientPromise; + } + + private async initializeUserDataSyncStore(authenticationSession: AuthenticationSessionInfo): Promise { + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore?.canSwitch) { + return; + } + + const disposables = new DisposableStore(); + try { + const userDataSyncStoreClient = disposables.add(new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService)); + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + + // Cache global state data for global state initialization + this.globalStateUserData = await userDataSyncStoreClient.readResource(SyncResource.GlobalState, null); + + if (this.globalStateUserData) { + const userDataSyncStoreType = new UserDataSyncStoreTypeSynchronizer(userDataSyncStoreClient, this.storageService, this.environmentService, this.fileService, this.logService).getSyncStoreType(this.globalStateUserData); + if (userDataSyncStoreType) { + await this.userDataSyncStoreManagementService.switch(userDataSyncStoreType); + + // Unset cached global state data if urls are changed + if (!isEqual(userDataSyncStore.url, this.userDataSyncStoreManagementService.userDataSyncStore?.url)) { + this.logService.info('Switched settings sync store'); + this.globalStateUserData = null; + } + } + } + } finally { + disposables.dispose(); + } + } + + async whenInitializationFinished(): Promise { + await this.initializationFinished.wait(); + } + + async requiresInitialization(): Promise { + this.logService.trace(`UserDataInitializationService#requiresInitialization`); + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + return !!userDataSyncStoreClient; + } + + async initializeRequiredResources(): Promise { + this.logService.trace(`UserDataInitializationService#initializeRequiredResources`); + return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); + } + + async initializeOtherResources(instantiationService: IInstantiationService): Promise { + try { + this.logService.trace(`UserDataInitializationService#initializeOtherResources`); + await Promise.allSettled([this.initialize([SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Tasks]), this.initializeExtensions(instantiationService)]); + } finally { + this.initializationFinished.open(); + } + } + + private async initializeExtensions(instantiationService: IInstantiationService): Promise { + try { + await Promise.all([this.initializeInstalledExtensions(instantiationService), this.initializeNewExtensions(instantiationService)]); + } finally { + this.initialized.push(SyncResource.Extensions); + } + } + + private initializeInstalledExtensionsPromise: Promise | undefined; + async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeInstalledExtensionsPromise) { + this.initializeInstalledExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeInstalledExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(InstalledExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeInstalledExtensionsPromise; + } + + private initializeNewExtensionsPromise: Promise | undefined; + private async initializeNewExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeNewExtensionsPromise) { + this.initializeNewExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeNewExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(NewExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeNewExtensionsPromise; + } + + private extensionsPreviewInitializerPromise: Promise | undefined; + private getExtensionsPreviewInitializer(instantiationService: IInstantiationService): Promise { + if (!this.extensionsPreviewInitializerPromise) { + this.extensionsPreviewInitializerPromise = (async () => { + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + if (!userDataSyncStoreClient) { + return null; + } + const userData = await userDataSyncStoreClient.readResource(SyncResource.Extensions, null); + return instantiationService.createInstance(ExtensionsPreviewInitializer, userData); + })(); + } + return this.extensionsPreviewInitializerPromise; + } + + private async initialize(syncResources: SyncResource[]): Promise { + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + if (!userDataSyncStoreClient) { + return; + } + + await Promises.settled(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); + const userData = await userDataSyncStoreClient.readResource(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : 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): IUserDataSyncResourceInitializer { + switch (syncResource) { + case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Tasks: return new TasksInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); + } + throw new Error(`Cannot create initializer for ${syncResource}`); + } + +} + +class ExtensionsPreviewInitializer extends AbstractExtensionsInitializer { + + private previewPromise: Promise | undefined; + private preview: IExtensionsInitializerPreviewResult | null = null; + + constructor( + private readonly extensionsData: IUserData, + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, + @IFileService fileService: IFileService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IStorageService storageService: IStorageService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + ) { + super(extensionManagementService, ignoredExtensionsManagementService, fileService, userDataProfilesService, environmentService, logService, storageService, uriIdentityService); + } + + getPreview(): Promise { + if (!this.previewPromise) { + this.previewPromise = super.initialize(this.extensionsData).then(() => this.preview); + } + return this.previewPromise; + } + + override initialize(): Promise { + throw new Error('should not be called directly'); + } + + protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteExtensions = await this.parseExtensions(remoteUserData); + if (!remoteExtensions) { + this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); + return; + } + const installedExtensions = await this.extensionManagementService.getInstalled(); + this.preview = this.generatePreview(remoteExtensions, installedExtensions); + } +} + +class InstalledExtensionsInitializer implements IUserDataSyncResourceInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + // 1. Initialise already installed extensions state + for (const installedExtension of preview.installedExtensions) { + const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); + if (syncExtension?.state) { + const extensionState = this.extensionStorageService.getExtensionState(installedExtension, true) || {}; + Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); + this.extensionStorageService.setExtensionState(installedExtension, extensionState, true); + } + } + + // 2. Initialise extensions enablement + if (preview.disabledExtensions.length) { + for (const identifier of preview.disabledExtensions) { + this.logService.trace(`Disabling extension...`, identifier.id); + await this.extensionEnablementService.disableExtension(identifier); + this.logService.info(`Disabling extension`, identifier.id); + } + } + } +} + +class NewExtensionsInitializer implements IUserDataSyncResourceInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + const newlyEnabledExtensions: ILocalExtension[] = []; + const targetPlatform = await this.extensionManagementService.getTargetPlatform(); + const galleryExtensions = await this.galleryService.getExtensions(preview.newExtensions, { targetPlatform, compatible: true }, CancellationToken.None); + for (const galleryExtension of galleryExtensions) { + try { + const extensionToSync = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, galleryExtension.identifier)); + if (!extensionToSync) { + continue; + } + if (extensionToSync.state) { + this.extensionStorageService.setExtensionState(galleryExtension, extensionToSync.state, true); + } + this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { + isMachineScoped: false, /* set isMachineScoped to prevent install and sync dialog in web */ + donotIncludePackAndDependencies: true, + installGivenVersion: !!extensionToSync.version, + installPreReleaseVersion: extensionToSync.preRelease + }); + if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { + newlyEnabledExtensions.push(local); + } + this.logService.info(`Installed extension.`, galleryExtension.identifier.id); + } catch (error) { + this.logService.error(error); + } + } + + const canEnabledExtensions = newlyEnabledExtensions.filter(e => this.extensionService.canAddExtension(toExtensionDescription(e))); + if (!(await this.areExtensionsRunning(canEnabledExtensions))) { + await new Promise((c, e) => { + const disposable = this.extensionService.onDidChangeExtensions(async () => { + try { + if (await this.areExtensionsRunning(canEnabledExtensions)) { + disposable.dispose(); + c(); + } + } catch (error) { + e(error); + } + }); + }); + } + } + + private async areExtensionsRunning(extensions: ILocalExtension[]): Promise { + await this.extensionService.whenInstalledExtensionsRegistered(); + const runningExtensions = this.extensionService.extensions; + return extensions.every(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier))); + } +} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 4fb365f2288..ed2f032ea8f 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -92,8 +92,9 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -registerSingleton(IUserDataInitializationService, UserDataInitializationService, InstantiationType.Delayed); +registerSingleton(IUserDataInitializationService, new SyncDescriptor(UserDataInitializationService, [[]], true)); registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService, InstantiationType.Delayed);