diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 3ee782d4e1b..55f0f189714 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -52,7 +52,7 @@ 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 { initializeUserData } from 'vs/workbench/services/userData/browser/userDataInit'; +import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; class BrowserMain extends Disposable { @@ -209,7 +209,9 @@ class BrowserMain extends Disposable { serviceCollection.set(IRequestService, requestService); // initialize user data - await initializeUserData(environmentService, fileService, storageService, productService, requestService, logService); + const userDataInitializationService = new UserDataInitializationService(environmentService, fileService, storageService, productService, requestService, logService); + serviceCollection.set(IUserDataInitializationService, userDataInitializationService); + await userDataInitializationService.initializeRequiredResources(); return { serviceCollection, logService, storageService }; } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index d0710e77fa2..9e979d28691 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -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(); - 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(); } diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index d1292573fd9..6500ffcbcc7 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -12,106 +12,159 @@ import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSyn 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +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 } from 'vs/platform/userDataSync/common/userDataSync'; +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 function initializeUserData( - environmentService: IWorkbenchEnvironmentService, - fileService: IFileService, - storageService: IStorageService, - productService: IProductService, - requestService: IRequestService, - logService: ILogService, -): Promise { - const initializers: AbstractInitializer[] = [ - new SettingsInitializer(fileService, environmentService, logService), - new KeybindingsInitializer(fileService, environmentService, logService), - new SnippetsInitializer(fileService, environmentService, logService), - new GlobalStateInitializer(storageService, fileService, environmentService, logService), - ]; - return initialize(initializers, 'user data', environmentService, fileService, storageService, productService, requestService, logService); +export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); +export interface IUserDataInitializationService { + _serviceBrand: any; + + initializeRequiredResources(): Promise; + initializeOtherResources(): Promise; + initializeExtensions(instantiationService: IInstantiationService): Promise; } -export function initializeExtensions(instantiationService: IInstantiationService): Promise { - return instantiationService.invokeFunction(accessor => { - return initialize([instantiationService.createInstance(ExtensionsInitializer)], 'extensions', - accessor.get(IWorkbenchEnvironmentService), - accessor.get(IFileService), - accessor.get(IStorageService), - accessor.get(IProductService), - accessor.get(IRequestService), - accessor.get(ILogService)); - }); -} +export class UserDataInitializationService implements IUserDataInitializationService { -async function initialize( - initializers: AbstractInitializer[], - userDataLabel: string, - environmentService: IWorkbenchEnvironmentService, - fileService: IFileService, - storageService: IStorageService, - productService: IProductService, - requestService: IRequestService, - logService: ILogService, -): Promise { + _serviceBrand: any; - if (!environmentService.options?.enableSyncByDefault) { - logService.trace(`Skipping initializing ${userDataLabel} as sync is not enabled by default`); - return; - } + private readonly initialized: SyncResource[] = []; - if (!storageService.isNew(StorageScope.GLOBAL)) { - logService.trace(`Skipping initializing ${userDataLabel} as application was opened before`); - return; - } + 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 + ) { } - if (!storageService.isNew(StorageScope.WORKSPACE)) { - logService.trace(`Skipping initializing ${userDataLabel} as workspace was opened before`); - return; - } + private _userDataSyncStoreClientPromise: Promise | undefined; + private createUserDataSyncStoreClient(): Promise { + if (!this._userDataSyncStoreClientPromise) { + this._userDataSyncStoreClientPromise = (async (): Promise => { + if (!isWeb) { + this.logService.trace(`Skipping initializing user data in desktop`); + return; + } - const userDataSyncStore = productService[CONFIGURATION_SYNC_STORE_KEY]; - if (!userDataSyncStore) { - logService.trace(`Skipping initializing ${userDataLabel} as sync service is not provided`); - return; - } + if (!this.environmentService.options?.enableSyncByDefault) { + this.logService.trace(`Skipping initializing user data as sync is not enabled by default`); + return; + } - if (!environmentService.options?.credentialsProvider) { - return; - } + if (!this.storageService.isNew(StorageScope.GLOBAL)) { + this.logService.trace(`Skipping initializing user data as application was opened before`); + return; + } - let authenticationSession; - try { - authenticationSession = await getAuthenticationSession(environmentService.options.credentialsProvider, productService); - } catch (error) { - logService.error(error); - } - if (!authenticationSession) { - logService.trace(`Skipping initializing ${userDataLabel} as authentication session is not set`); - return; - } + if (!this.storageService.isNew(StorageScope.WORKSPACE)) { + this.logService.trace(`Skipping initializing user data as workspace was opened before`); + return; + } - const userDataSyncStoreClient = new UserDataSyncStoreClient(URI.parse(userDataSyncStore.url), productService, requestService, logService, environmentService, fileService, storageService); - try { - userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); - logService.info(`Started initializing ${userDataLabel}`); - for (const initializer of initializers) { - try { - const userData = await userDataSyncStoreClient.read(initializer.resource, null); - await initializer.initialize(userData); - } catch (error) { - logService.info(`Error while initializing ${initializer.resource}`); - logService.error(error); - } + 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; + })(); } - logService.info(`Initializing ${userDataLabel} completed`); - } finally { - userDataSyncStoreClient.dispose(); + + return this._userDataSyncStoreClientPromise; + } + + async initializeRequiredResources(): Promise { + return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); + } + + async initializeOtherResources(): Promise { + return this.initialize([SyncResource.Keybindings, SyncResource.Snippets]); + } + + async initializeExtensions(instantiationService: IInstantiationService): Promise { + return this.initialize([SyncResource.Extensions], instantiationService); + } + + private async initialize(syncResources: SyncResource[], instantiationService?: IInstantiationService): Promise { + 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(Extensions.Workbench); + workbenchRegistry.registerWorkbenchContribution(InitializeOtherResourcesContribution, LifecyclePhase.Eventually); +} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index d52fd0e9c7a..b436aed9f7a 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -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