Split up NotebookProvider into separate providers for handling file serialization and cell execution. (#17176)

This commit is contained in:
Cory Rivera
2021-09-29 16:15:28 -07:00
committed by GitHub
parent dfc2635aa7
commit 14904bb671
51 changed files with 1426 additions and 971 deletions

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { IContentManager } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { IContentLoader } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
export interface INotebookInput {
@@ -14,11 +14,12 @@ export interface INotebookInput {
isDirty(): boolean;
setDirty(boolean);
readonly notebookUri: URI;
updateModel(): void;
updateModel(): Promise<void>;
readonly editorOpenedTimestamp: number;
readonly layoutChanged: Event<void>;
readonly contentManager: IContentManager;
readonly contentLoader: IContentLoader;
readonly standardKernels: IStandardKernelWithProvider[];
readonly providersLoaded: Promise<void>;
}
export function isINotebookInput(value: any): value is INotebookInput {
@@ -29,7 +30,7 @@ export function isINotebookInput(value: any): value is INotebookInput {
typeof value.isDirty === 'function' &&
typeof value.layoutChanged === 'function' &&
typeof value.editorOpenedTimestamp === 'number' &&
typeof value.contentManager === 'object' &&
typeof value.contentLoader === 'object' &&
typeof value.standardKernels === 'object') {
return true;
}

View File

@@ -13,7 +13,7 @@ import { getErrorMessage } from 'vs/base/common/errors';
import { IClientSession, IClientSessionOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { Deferred } from 'sql/base/common/promise';
import { INotebookManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { IExecuteManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
type KernelChangeHandler = (kernel: nb.IKernelChangedArgs) => Promise<void>;
@@ -45,14 +45,14 @@ export class ClientSession implements IClientSession {
private _serverLoadFinished: Promise<void> = Promise.resolve();
private _session: nb.ISession | undefined;
private isServerStarted: boolean = false;
private notebookManager: INotebookManager;
private _isServerStarted: boolean = false;
private _executeManager: IExecuteManager;
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
private _connectionId: string = '';
constructor(private options: IClientSessionOptions) {
this._notebookUri = options.notebookUri;
this.notebookManager = options.notebookManager;
this._executeManager = options.executeManager;
this._isReady = false;
this._ready = new Deferred<void>();
this._kernelChangeCompleted = new Deferred<void>();
@@ -77,23 +77,23 @@ export class ClientSession implements IClientSession {
}
private async startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
let serverManager = this.notebookManager.serverManager;
let serverManager = this._executeManager.serverManager;
if (serverManager) {
await serverManager.startServer(kernelSpec);
if (!serverManager.isStarted) {
throw new Error(localize('ServerNotStarted', "Server did not start for unknown reason"));
}
this.isServerStarted = serverManager.isStarted;
this._isServerStarted = serverManager.isStarted;
} else {
this.isServerStarted = true;
this._isServerStarted = true;
}
}
private async initializeSession(): Promise<void> {
await this._serverLoadFinished;
if (this.isServerStarted) {
if (!this.notebookManager.sessionManager.isReady) {
await this.notebookManager.sessionManager.ready;
if (this._isServerStarted) {
if (!this._executeManager.sessionManager.isReady) {
await this._executeManager.sessionManager.ready;
}
if (this._defaultKernel) {
await this.startSessionInstance(this._defaultKernel.name);
@@ -105,7 +105,7 @@ export class ClientSession implements IClientSession {
let session: nb.ISession;
try {
// TODO #3164 should use URI instead of path for startNew
session = await this.notebookManager.sessionManager.startNew({
session = await this._executeManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: kernelName
// TODO add kernel name if saved in the document
@@ -115,7 +115,7 @@ export class ClientSession implements IClientSession {
// TODO move registration
if (err && err.response && err.response.status === 501) {
this.options.notificationService.warn(localize('kernelRequiresConnection', "Kernel {0} was not found. The default kernel will be used instead.", kernelName));
session = await this.notebookManager.sessionManager.startNew({
session = await this._executeManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: undefined
});
@@ -304,8 +304,8 @@ export class ClientSession implements IClientSession {
*/
public async shutdown(): Promise<void> {
// Always try to shut down session
if (this._session && this._session.id && this.notebookManager && this.notebookManager.sessionManager) {
await this.notebookManager.sessionManager.shutdown(this._session.id);
if (this._session && this._session.id && this._executeManager && this._executeManager.sessionManager) {
await this._executeManager.sessionManager.shutdown(this._session.id);
}
}

View File

@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CellType, NotebookChangeType } from 'sql/workbench/services/notebook/common/contracts';
import { INotebookManager, ILanguageMagic } from 'sql/workbench/services/notebook/browser/notebookService';
import { IExecuteManager, ILanguageMagic, ISerializationManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils';
@@ -42,7 +42,7 @@ export interface ISingleNotebookEditOperation {
export interface IClientSessionOptions {
notebookUri: URI;
notebookManager: INotebookManager;
executeManager: IExecuteManager;
notificationService: INotificationService;
kernelSpec: nb.IKernelSpec;
}
@@ -254,9 +254,14 @@ export interface INotebookModel {
readonly language: string;
/**
* All notebook managers applicable for a given notebook
* The current serialization manager applicable for a given notebook
*/
readonly notebookManagers: INotebookManager[];
readonly serializationManager: ISerializationManager | undefined;
/**
* All execute managers applicable for a given notebook
*/
readonly executeManagers: IExecuteManager[];
/**
* Event fired on first initialization of the kernel and
@@ -551,7 +556,7 @@ export interface IModelFactory {
createClientSession(options: IClientSessionOptions): IClientSession;
}
export interface IContentManager {
export interface IContentLoader {
/**
* This is a specialized method intended to load for a default context - just the current Notebook's URI
*/
@@ -569,8 +574,9 @@ export interface INotebookModelOptions {
*/
factory: IModelFactory;
contentManager: IContentManager;
notebookManagers: INotebookManager[];
contentLoader: IContentLoader;
serializationManagers: ISerializationManager[];
executeManagers: IExecuteManager[];
providerId: string;
defaultKernel: nb.IKernelSpec;
cellMagicMapper: ICellMagicMapper;

View File

@@ -14,7 +14,7 @@ import { NotebookChangeType, CellType, CellTypes } from 'sql/workbench/services/
import { KernelsLanguage, nbversion } from 'sql/workbench/services/notebook/common/notebookConstants';
import * as notebookUtils from 'sql/workbench/services/notebook/browser/models/notebookUtils';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { IExecuteManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER, ISerializationManager } from 'sql/workbench/services/notebook/browser/notebookService';
import { NotebookContexts } from 'sql/workbench/services/notebook/browser/models/notebookContexts';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { INotification, Severity, INotificationService } from 'vs/platform/notification/common/notification';
@@ -124,7 +124,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
) {
super();
if (!_notebookOptions || !_notebookOptions.notebookUri || !_notebookOptions.notebookManagers) {
if (!_notebookOptions || !_notebookOptions.notebookUri || !_notebookOptions.executeManagers) {
throw new Error('path or notebook service not defined');
}
this._trustedMode = false;
@@ -136,27 +136,43 @@ export class NotebookModel extends Disposable implements INotebookModel {
this._defaultKernel = _notebookOptions.defaultKernel;
}
public get notebookManagers(): INotebookManager[] {
let notebookManagers = this._notebookOptions.notebookManagers.filter(manager => manager.providerId !== DEFAULT_NOTEBOOK_PROVIDER);
if (!notebookManagers.length) {
return this._notebookOptions.notebookManagers;
private get serializationManagers(): ISerializationManager[] {
let managers = this._notebookOptions.serializationManagers.filter(manager => manager.providerId !== DEFAULT_NOTEBOOK_PROVIDER);
if (!managers.length) {
return this._notebookOptions.serializationManagers;
}
return notebookManagers;
return managers;
}
public get notebookManager(): INotebookManager | undefined {
let manager = this.notebookManagers.find(manager => manager.providerId === this._providerId);
public get serializationManager(): ISerializationManager | undefined {
let manager = this.serializationManagers.find(manager => manager.providerId === this._providerId);
if (!manager) {
// Note: this seems like a less than ideal scenario. We should ideally pass in the "correct" provider ID and allow there to be a default,
// instead of assuming in the NotebookModel constructor that the option is either SQL or Jupyter
manager = this.notebookManagers.find(manager => manager.providerId === DEFAULT_NOTEBOOK_PROVIDER);
manager = this.serializationManagers.find(manager => manager.providerId === DEFAULT_NOTEBOOK_PROVIDER);
}
return manager;
}
public getNotebookManager(providerId: string): INotebookManager | undefined {
public get executeManagers(): IExecuteManager[] {
let managers = this._notebookOptions.executeManagers.filter(manager => manager.providerId !== DEFAULT_NOTEBOOK_PROVIDER);
if (!managers.length) {
return this._notebookOptions.executeManagers;
}
return managers;
}
public get executeManager(): IExecuteManager | undefined {
let manager = this.executeManagers.find(manager => manager.providerId === this._providerId);
if (!manager) {
// Note: this seems like a less than ideal scenario. We should ideally pass in the "correct" provider ID and allow there to be a default,
// instead of assuming in the NotebookModel constructor that the option is either SQL or Jupyter
manager = this.executeManagers.find(manager => manager.providerId === DEFAULT_NOTEBOOK_PROVIDER);
}
return manager;
}
public getExecuteManager(providerId: string): IExecuteManager | undefined {
if (providerId) {
return this.notebookManagers.find(manager => manager.providerId === providerId);
return this.executeManagers.find(manager => manager.providerId === providerId);
}
return undefined;
}
@@ -174,7 +190,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
public get hasServerManager(): boolean {
// If the service has a server manager, then we can show the start button
return !!this.notebookManager?.serverManager;
return !!this.executeManager?.serverManager;
}
public get contentChanged(): Event<NotebookContentChange> {
@@ -247,7 +263,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
defaultKernel: '',
kernels: []
};
this.notebookManagers.forEach(manager => {
this.executeManagers.forEach(manager => {
if (manager.sessionManager && manager.sessionManager.specs && manager.sessionManager.specs.kernels) {
manager.sessionManager.specs.kernels.forEach(kernel => {
specs.kernels.push(kernel);
@@ -399,8 +415,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
let contents: nb.INotebookContents | undefined;
if (this._notebookOptions && this._notebookOptions.contentManager) {
contents = await this._notebookOptions.contentManager.loadContent();
if (this._notebookOptions && this._notebookOptions.contentLoader) {
contents = await this._notebookOptions.contentLoader.loadContent();
}
let factory = this._notebookOptions.factory;
// if cells already exist, create them with language info (if it is saved)
@@ -693,7 +709,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
this._onErrorEmitter.fire({ message: error, severity: Severity.Error });
}
public async startSession(manager: INotebookManager, displayName?: string, setErrorStateOnFail?: boolean, kernelAlias?: string): Promise<void> {
public async startSession(manager: IExecuteManager, displayName?: string, setErrorStateOnFail?: boolean, kernelAlias?: string): Promise<void> {
if (displayName) {
let standardKernel = this._standardKernels.find(kernel => kernel.displayName === displayName);
if (standardKernel) {
@@ -703,7 +719,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (this._defaultKernel) {
let clientSession = this._notebookOptions.factory.createClientSession({
notebookUri: this._notebookOptions.notebookUri,
notebookManager: manager,
executeManager: manager,
notificationService: this._notebookOptions.notificationService,
kernelSpec: this._defaultKernel
});
@@ -750,7 +766,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (this._activeClientSession) {
// Old active client sessions have already been shutdown by RESTART_JUPYTER_NOTEBOOK_SESSIONS command
this._activeClientSession = undefined;
await this.startSession(this.notebookManager, this._selectedKernelDisplayName, true);
await this.startSession(this.executeManager, this._selectedKernelDisplayName, true);
}
}
@@ -777,7 +793,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
if (provider && provider !== this._providerId) {
this._providerId = provider;
} else if (!provider) {
this.notebookOptions.notebookManagers.forEach(m => {
this.notebookOptions.executeManagers.forEach(m => {
if (m.providerId !== SQL_NOTEBOOK_PROVIDER) {
// We don't know which provider it is before that provider is chosen to query its specs. Choosing the "last" one registered.
this._providerId = m.providerId;
@@ -1002,7 +1018,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
// Ensure that the kernel we try to switch to is a valid kernel; if not, use the default
let kernelSpecs = this.getKernelSpecs();
if (kernelSpecs && kernelSpecs.length > 0 && kernelSpecs.findIndex(k => k.display_name === spec.display_name) < 0) {
spec = kernelSpecs.find(spec => spec.name === this.notebookManager?.sessionManager.specs.defaultKernel);
spec = kernelSpecs.find(spec => spec.name === this.executeManager?.sessionManager.specs.defaultKernel);
}
}
else {
@@ -1099,11 +1115,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
}
public getDisplayNameFromSpecName(kernel: nb.IKernel): string | undefined {
let specs = this.notebookManager?.sessionManager.specs;
let specs = this.executeManager?.sessionManager.specs;
if (!specs || !specs.kernels) {
return kernel.name;
}
let newKernel = this.notebookManager.sessionManager.specs.kernels.find(k => k.name === kernel.name);
let newKernel = this.executeManager.sessionManager.specs.kernels.find(k => k.name === kernel.name);
let newKernelDisplayName;
if (newKernel) {
newKernelDisplayName = newKernel.display_name;
@@ -1202,7 +1218,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
this._onProviderIdChanged.fire(this._providerId);
await this.shutdownActiveSession();
let manager = this.getNotebookManager(providerId);
let manager = this.getExecuteManager(providerId);
if (manager) {
await this.startSession(manager, displayName, false, kernelAlias);
} else {
@@ -1225,8 +1241,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
return providerId;
}
} else {
if (this.notebookManagers?.length) {
return this.notebookManagers.map(m => m.providerId).find(p => p !== DEFAULT_NOTEBOOK_PROVIDER && p !== SQL_NOTEBOOK_PROVIDER);
if (this.executeManagers?.length) {
return this.executeManagers.map(m => m.providerId).find(p => p !== DEFAULT_NOTEBOOK_PROVIDER && p !== SQL_NOTEBOOK_PROVIDER);
}
}
return undefined;
@@ -1234,9 +1250,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
// Get kernel specs from current sessionManager
private getKernelSpecs(): nb.IKernelSpec[] {
if (this.notebookManager && this.notebookManager.sessionManager && this.notebookManager.sessionManager.specs &&
this.notebookManager.sessionManager.specs.kernels) {
return this.notebookManager.sessionManager.specs.kernels;
if (this.executeManager && this.executeManager.sessionManager && this.executeManager.sessionManager.specs &&
this.executeManager.sessionManager.specs.kernels) {
return this.executeManager.sessionManager.specs.kernels;
}
return [];
}

View File

@@ -57,15 +57,14 @@ export interface INotebookService {
readonly isRegistrationComplete: boolean;
readonly registrationComplete: Promise<void>;
readonly languageMagics: ILanguageMagic[];
/**
* Register a metadata provider
*/
registerProvider(providerId: string, provider: INotebookProvider): void;
/**
* Register a metadata provider
*/
unregisterProvider(providerId: string): void;
registerSerializationProvider(providerId: string, provider: ISerializationProvider): void;
registerExecuteProvider(providerId: string, provider: IExecuteProvider): void;
unregisterSerializationProvider(providerId: string): void;
unregisterExecuteProvider(providerId: string): void;
registerNavigationProvider(provider: INavigationProvider): void;
@@ -77,14 +76,9 @@ export interface INotebookService {
getStandardKernelsForProvider(provider: string): azdata.nb.IStandardKernel[];
/**
* Initializes and returns a Notebook manager that can handle all important calls to open, display, and
* run cells in a notebook.
* @param providerId ID for the provider to be used to instantiate a backend notebook service
* @param uri URI for a notebook that is to be opened. Based on this an existing manager may be used, or
* a new one may need to be created
*/
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
getOrCreateSerializationManager(providerId: string, uri: URI): Promise<ISerializationManager>;
getOrCreateExecuteManager(providerId: string, uri: URI): Thenable<IExecuteManager>;
addNotebookEditor(editor: INotebookEditor): void;
@@ -148,15 +142,24 @@ export interface INotebookService {
getUntitledUriPath(originalTitle: string): string;
}
export interface INotebookProvider {
export interface IExecuteProvider {
readonly providerId: string;
getNotebookManager(notebookUri: URI): Thenable<INotebookManager>;
getExecuteManager(notebookUri: URI): Thenable<IExecuteManager>;
handleNotebookClosed(notebookUri: URI): void;
}
export interface INotebookManager {
export interface ISerializationProvider {
readonly providerId: string;
getSerializationManager(notebookUri: URI): Thenable<ISerializationManager>;
}
export interface ISerializationManager {
providerId: string;
readonly contentManager: azdata.nb.ContentManager;
}
export interface IExecuteManager {
providerId: string;
readonly sessionManager: azdata.nb.SessionManager;
readonly serverManager: azdata.nb.ServerManager;
}

View File

@@ -9,12 +9,12 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform';
import {
INotebookService, INotebookManager, INotebookProvider,
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER, INavigationProvider, ILanguageMagic, NavigationProviders, unsavedBooksContextKey
INotebookService, IExecuteManager, IExecuteProvider,
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor, SQL_NOTEBOOK_PROVIDER, INavigationProvider, ILanguageMagic, NavigationProviders, unsavedBooksContextKey, ISerializationProvider, ISerializationManager
} from 'sql/workbench/services/notebook/browser/notebookService';
import { RenderMimeRegistry } from 'sql/workbench/services/notebook/browser/outputs/registry';
import { standardRendererFactories } from 'sql/workbench/services/notebook/browser/outputs/factories';
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistration } from 'sql/workbench/services/notebook/common/notebookRegistry';
import { Extensions, INotebookProviderRegistry, NotebookProviderRegistryId, ProviderDescriptionRegistration } from 'sql/workbench/services/notebook/common/notebookRegistry';
import { Emitter, Event } from 'vs/base/common/event';
import { Memento } from 'vs/workbench/common/memento';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@@ -26,7 +26,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
import { ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { SqlNotebookProvider } from 'sql/workbench/services/notebook/browser/sql/sqlNotebookProvider';
import { SqlExecuteProvider } from 'sql/workbench/services/notebook/browser/sql/sqlExecuteProvider';
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { ILogService } from 'vs/platform/log/common/log';
@@ -50,8 +50,9 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor
import { IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
import { isINotebookInput } from 'sql/workbench/services/notebook/browser/interface';
import { INotebookShowOptions } from 'sql/workbench/api/common/sqlExtHost.protocol';
import { NotebookLanguage } from 'sql/workbench/common/constants';
import { JUPYTER_PROVIDER_ID, NotebookLanguage } from 'sql/workbench/common/constants';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { SqlSerializationProvider } from 'sql/workbench/services/notebook/browser/sql/sqlSerializationProvider';
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
@@ -65,7 +66,8 @@ interface NotebookProviderCache {
}
export interface NotebookProvidersMemento {
notebookProviderCache: NotebookProviderCache;
notebookSerializationProviderCache: NotebookProviderCache;
notebookExecuteProviderCache: NotebookProviderCache;
}
interface TrustedNotebookMetadata {
@@ -80,24 +82,53 @@ export interface TrustedNotebooksMemento {
trustedNotebooksCache: TrustedNotebookCache;
}
const notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
const notebookRegistry = Registry.as<INotebookProviderRegistry>(NotebookProviderRegistryId);
export class ProviderDescriptor {
private _instanceReady = new Deferred<INotebookProvider>();
constructor(private _instance?: INotebookProvider) {
export class SerializationProviderDescriptor {
private _instanceReady = new Deferred<ISerializationProvider>();
constructor(private readonly _providerId: string, private _instance?: ISerializationProvider) {
if (_instance) {
this._instanceReady.resolve(_instance);
}
}
public get instanceReady(): Promise<INotebookProvider> {
public get providerId(): string {
return this._providerId;
}
public get instanceReady(): Promise<ISerializationProvider> {
return this._instanceReady.promise;
}
public get instance(): INotebookProvider {
public get instance(): ISerializationProvider | undefined {
return this._instance;
}
public set instance(value: INotebookProvider) {
public set instance(value: ISerializationProvider) {
this._instance = value;
this._instanceReady.resolve(value);
}
}
export class ExecuteProviderDescriptor {
private _instanceReady = new Deferred<IExecuteProvider>();
constructor(private readonly _providerId: string, private _instance?: IExecuteProvider) {
if (_instance) {
this._instanceReady.resolve(_instance);
}
}
public get providerId(): string {
return this._providerId;
}
public get instanceReady(): Promise<IExecuteProvider> {
return this._instanceReady.promise;
}
public get instance(): IExecuteProvider | undefined {
return this._instance;
}
public set instance(value: IExecuteProvider) {
this._instance = value;
this._instanceReady.resolve(value);
}
@@ -113,14 +144,16 @@ export class NotebookService extends Disposable implements INotebookService {
private _providersMemento: Memento;
private _trustedNotebooksMemento: Memento;
private _mimeRegistry: RenderMimeRegistry;
private _providers: Map<string, ProviderDescriptor> = new Map();
private _serializationProviders: Map<string, SerializationProviderDescriptor> = new Map();
private _executeProviders: Map<string, ExecuteProviderDescriptor> = new Map();
private _navigationProviders: Map<string, INavigationProvider> = new Map();
private _managersMap: Map<string, INotebookManager[]> = new Map();
private _serializationManagersMap: Map<string, ISerializationManager[]> = new Map();
private _executeManagersMap: Map<string, IExecuteManager[]> = new Map();
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
private _onNotebookEditorRename = new Emitter<INotebookEditor>();
private _editors = new Map<string, INotebookEditor>();
private _fileToProviders = new Map<string, NotebookProviderRegistration[]>();
private _fileToProviderDescriptions = new Map<string, ProviderDescriptionRegistration[]>();
private _providerToStandardKernels = new Map<string, nb.IStandardKernel[]>();
private _registrationComplete = new Deferred<void>();
private _isRegistrationComplete = false;
@@ -147,19 +180,24 @@ export class NotebookService extends Disposable implements INotebookService {
super();
this._providersMemento = new Memento('notebookProviders', this._storageService);
this._trustedNotebooksMemento = new Memento(TrustedNotebooksMementoId, this._storageService);
if (this._storageService !== undefined && this.providersMemento.notebookProviderCache === undefined) {
this.providersMemento.notebookProviderCache = <NotebookProviderCache>{};
if (this._storageService !== undefined) {
if (this.providersMemento.notebookSerializationProviderCache === undefined) {
this.providersMemento.notebookSerializationProviderCache = <NotebookProviderCache>{};
}
if (this.providersMemento.notebookExecuteProviderCache === undefined) {
this.providersMemento.notebookExecuteProviderCache = <NotebookProviderCache>{};
}
}
this._register(notebookRegistry.onNewRegistration(this.updateRegisteredProviders, this));
this.registerBuiltInProvider();
this._register(notebookRegistry.onNewDescriptionRegistration(this.handleNewProviderDescriptions, this));
this.registerBuiltInProviders();
// If a provider has been already registered, the onNewRegistration event will not have a listener attached yet
// So, explicitly updating registered providers here.
if (notebookRegistry.providers.length > 0) {
notebookRegistry.providers.forEach(p => {
if (notebookRegistry.providerDescriptions.length > 0) {
notebookRegistry.providerDescriptions.forEach(p => {
// Don't need to re-register SQL_NOTEBOOK_PROVIDER
if (p.provider !== SQL_NOTEBOOK_PROVIDER) {
this.updateRegisteredProviders({ id: p.provider, registration: p });
this.handleNewProviderDescriptions({ id: p.provider, registration: p });
}
});
}
@@ -266,12 +304,15 @@ export class NotebookService extends Disposable implements INotebookService {
this._registrationComplete.resolve();
}
private updateRegisteredProviders(p: { id: string; registration: NotebookProviderRegistration }) {
let registration = p.registration;
if (!this._providers.has(p.id)) {
this._providers.set(p.id, new ProviderDescriptor());
private handleNewProviderDescriptions(p: { id: string; registration: ProviderDescriptionRegistration }) {
// Skip adding a Serialization descriptor for Jupyter, since we use the default notebook Serialization provider for jupyter
if (!this._serializationProviders.has(p.id) && p.id !== JUPYTER_PROVIDER_ID) {
this._serializationProviders.set(p.id, new SerializationProviderDescriptor(p.id));
}
if (!this._executeProviders.has(p.id)) {
this._executeProviders.set(p.id, new ExecuteProviderDescriptor(p.id));
}
let registration = p.registration;
if (registration.fileExtensions) {
if (Array.isArray(registration.fileExtensions)) {
for (let fileType of registration.fileExtensions) {
@@ -287,18 +328,32 @@ export class NotebookService extends Disposable implements INotebookService {
}
}
registerProvider(providerId: string, instance: INotebookProvider): void {
let providerDescriptor = this._providers.get(providerId);
registerSerializationProvider(providerId: string, instance: ISerializationProvider): void {
let providerDescriptor = this._serializationProviders.get(providerId);
if (providerDescriptor) {
// Update, which will resolve the promise for anyone waiting on the instance to be registered
providerDescriptor.instance = instance;
} else {
this._providers.set(providerId, new ProviderDescriptor(instance));
this._serializationProviders.set(providerId, new SerializationProviderDescriptor(providerId, instance));
}
}
unregisterProvider(providerId: string): void {
this._providers.delete(providerId);
registerExecuteProvider(providerId: string, instance: IExecuteProvider): void {
let providerDescriptor = this._executeProviders.get(providerId);
if (providerDescriptor) {
// Update, which will resolve the promise for anyone waiting on the instance to be registered
providerDescriptor.instance = instance;
} else {
this._executeProviders.set(providerId, new ExecuteProviderDescriptor(providerId, instance));
}
}
unregisterSerializationProvider(providerId: string): void {
this._serializationProviders.delete(providerId);
}
unregisterExecuteProvider(providerId: string): void {
this._executeProviders.delete(providerId);
}
registerNavigationProvider(provider: INavigationProvider): void {
@@ -322,20 +377,20 @@ export class NotebookService extends Disposable implements INotebookService {
return this._registrationComplete.promise;
}
private addFileProvider(fileType: string, provider: NotebookProviderRegistration) {
let providers = this._fileToProviders.get(fileType.toUpperCase());
private addFileProvider(fileType: string, provider: ProviderDescriptionRegistration) {
let providers = this._fileToProviderDescriptions.get(fileType.toUpperCase());
if (!providers) {
providers = [];
}
providers.push(provider);
this._fileToProviders.set(fileType.toUpperCase(), providers);
this._fileToProviderDescriptions.set(fileType.toUpperCase(), providers);
}
// Standard kernels are contributed where a list of kernels are defined that can be shown
// in the kernels dropdown list before a SessionManager has been started; this way,
// every NotebookProvider doesn't need to have an active SessionManager in order to contribute
// kernels to the dropdown
private addStandardKernels(provider: NotebookProviderRegistration) {
private addStandardKernels(provider: ProviderDescriptionRegistration) {
let providerUpperCase = provider.provider.toUpperCase();
let standardKernels = this._providerToStandardKernels.get(providerUpperCase);
if (!standardKernels) {
@@ -356,12 +411,12 @@ export class NotebookService extends Disposable implements INotebookService {
}
getSupportedFileExtensions(): string[] {
return Array.from(this._fileToProviders.keys());
return Array.from(this._fileToProviderDescriptions.keys());
}
getProvidersForFileType(fileType: string): string[] {
fileType = fileType.toUpperCase();
let providers = this._fileToProviders.get(fileType);
let providers = this._fileToProviderDescriptions.get(fileType);
return providers ? providers.map(provider => provider.provider) : undefined;
}
@@ -371,7 +426,7 @@ export class NotebookService extends Disposable implements INotebookService {
}
private shutdown(): void {
this._managersMap.forEach(manager => {
this._executeManagersMap.forEach(manager => {
manager.forEach(m => {
if (m.serverManager) {
// TODO should this thenable be awaited?
@@ -381,12 +436,12 @@ export class NotebookService extends Disposable implements INotebookService {
});
}
async getOrCreateNotebookManager(providerId: string, uri: URI): Promise<INotebookManager> {
async getOrCreateSerializationManager(providerId: string, uri: URI): Promise<ISerializationManager> {
if (!uri) {
throw new Error(NotebookUriNotDefined);
}
let uriString = uri.toString();
let managers: INotebookManager[] = this._managersMap.get(uriString);
let managers: ISerializationManager[] = this._serializationManagersMap.get(uriString);
// If manager already exists for a given notebook, return it
if (managers) {
let index = managers.findIndex(m => m.providerId === providerId);
@@ -394,11 +449,32 @@ export class NotebookService extends Disposable implements INotebookService {
return managers[index];
}
}
let newManager = await this.doWithProvider(providerId, (provider) => provider.getNotebookManager(uri));
let newManager = await this.doWithSerializationProvider(providerId, (provider) => provider.getSerializationManager(uri));
managers = managers || [];
managers.push(newManager);
this._managersMap.set(uriString, managers);
this._serializationManagersMap.set(uriString, managers);
return newManager;
}
async getOrCreateExecuteManager(providerId: string, uri: URI): Promise<IExecuteManager> {
if (!uri) {
throw new Error(NotebookUriNotDefined);
}
let uriString = uri.toString();
let managers: IExecuteManager[] = this._executeManagersMap.get(uriString);
// If manager already exists for a given notebook, return it
if (managers) {
let index = managers.findIndex(m => m.providerId === providerId);
if (index >= 0) {
return managers[index];
}
}
let newManager = await this.doWithExecuteProvider(providerId, (provider) => provider.getExecuteManager(uri));
managers = managers || [];
managers.push(newManager);
this._executeManagersMap.set(uriString, managers);
return newManager;
}
@@ -461,26 +537,32 @@ export class NotebookService extends Disposable implements INotebookService {
private sendNotebookCloseToProvider(editor: INotebookEditor): void {
let notebookUri = editor.notebookParams.notebookUri;
let uriString = notebookUri.toString();
let managers = this._managersMap.get(uriString);
let managers = this._executeManagersMap.get(uriString);
if (managers) {
// As we have a manager, we can assume provider is ready
this._managersMap.delete(uriString);
this._executeManagersMap.delete(uriString);
managers.forEach(m => {
let provider = this._providers.get(m.providerId);
provider.instance.handleNotebookClosed(notebookUri);
let provider = this._executeProviders.get(m.providerId);
provider?.instance?.handleNotebookClosed(notebookUri);
});
}
}
private async doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Promise<T> {
private async doWithSerializationProvider<T>(providerId: string, op: (provider: ISerializationProvider) => Thenable<T>): Promise<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider: INotebookProvider = await this.getProviderInstance(providerId);
let provider: ISerializationProvider = await this.getSerializationProviderInstance(providerId);
return op(provider);
}
private async getProviderInstance(providerId: string, timeout?: number): Promise<INotebookProvider> {
let providerDescriptor = this._providers.get(providerId);
let instance: INotebookProvider;
private async doWithExecuteProvider<T>(providerId: string, op: (provider: IExecuteProvider) => Thenable<T>): Promise<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider: IExecuteProvider = await this.getExecuteProviderInstance(providerId);
return op(provider);
}
private async getSerializationProviderInstance(providerId: string, timeout?: number): Promise<ISerializationProvider> {
let providerDescriptor = this._serializationProviders.get(providerId);
let instance: ISerializationProvider;
// Try get from actual provider, waiting on its registration
if (providerDescriptor) {
@@ -491,7 +573,7 @@ export class NotebookService extends Disposable implements INotebookService {
} catch (error) {
this._logService.error(error);
}
instance = await this.waitOnProviderAvailability(providerDescriptor, timeout);
instance = await this.waitOnSerializationProviderAvailability(providerDescriptor, timeout);
} else {
instance = providerDescriptor.instance;
}
@@ -499,7 +581,7 @@ export class NotebookService extends Disposable implements INotebookService {
// Fall back to default (SQL) if this failed
if (!instance) {
providerDescriptor = this._providers.get(SQL_NOTEBOOK_PROVIDER);
providerDescriptor = this._serializationProviders.get(SQL_NOTEBOOK_PROVIDER);
instance = providerDescriptor ? providerDescriptor.instance : undefined;
}
@@ -510,12 +592,60 @@ export class NotebookService extends Disposable implements INotebookService {
return instance;
}
private waitOnProviderAvailability(providerDescriptor: ProviderDescriptor, timeout?: number): Promise<INotebookProvider> {
// Wait up to 30 seconds for the provider to be registered
timeout = timeout ?? 30000;
let promises: Promise<INotebookProvider>[] = [
private async getExecuteProviderInstance(providerId: string, timeout?: number): Promise<IExecuteProvider> {
let providerDescriptor = this._executeProviders.get(providerId);
let instance: IExecuteProvider;
// Try get from actual provider, waiting on its registration
if (providerDescriptor) {
if (!providerDescriptor.instance) {
// Await extension registration before awaiting provider registration
try {
await this._extensionService.whenInstalledExtensionsRegistered();
} catch (error) {
this._logService.error(error);
}
instance = await this.waitOnExecuteProviderAvailability(providerDescriptor, timeout);
} else {
instance = providerDescriptor.instance;
}
}
// Fall back to default (SQL) if this failed
if (!instance) {
providerDescriptor = this._executeProviders.get(SQL_NOTEBOOK_PROVIDER);
instance = providerDescriptor ? providerDescriptor.instance : undefined;
}
// Should never happen, but if default wasn't registered we should throw
if (!instance) {
throw new Error(NotebookServiceNoProviderRegistered);
}
return instance;
}
private waitOnSerializationProviderAvailability(providerDescriptor: SerializationProviderDescriptor, timeout?: number): Promise<ISerializationProvider | undefined> {
// Wait up to 10 seconds for the provider to be registered
timeout = timeout ?? 10000;
let promises: Promise<ISerializationProvider>[] = [
providerDescriptor.instanceReady,
new Promise<INotebookProvider>((resolve, reject) => setTimeout(() => resolve(undefined), timeout))
new Promise<ISerializationProvider>((resolve, reject) => setTimeout(() => {
this._serializationProviders.delete(providerDescriptor.providerId);
return resolve(undefined);
}, timeout))
];
return Promise.race(promises);
}
private waitOnExecuteProviderAvailability(providerDescriptor: ExecuteProviderDescriptor, timeout?: number): Promise<IExecuteProvider | undefined> {
// Wait up to 10 seconds for the provider to be registered
timeout = timeout ?? 10000;
let promises: Promise<IExecuteProvider>[] = [
providerDescriptor.instanceReady,
new Promise<IExecuteProvider>((resolve, reject) => setTimeout(() => {
this._executeProviders.delete(providerDescriptor.providerId);
return resolve(undefined);
}, timeout))
];
return Promise.race(promises);
}
@@ -543,36 +673,48 @@ export class NotebookService extends Disposable implements INotebookService {
}
private cleanupProviders(): void {
let knownProviders = Object.keys(notebookRegistry.providers);
let cache = this.providersMemento.notebookProviderCache;
for (let key in cache) {
let knownProviders = notebookRegistry.providerDescriptions.map(d => d.provider);
let executeCache = this.providersMemento.notebookExecuteProviderCache;
for (let key in executeCache) {
if (!knownProviders.some(x => x === key)) {
this._providers.delete(key);
delete cache[key];
this._executeProviders.delete(key);
delete executeCache[key];
}
}
let serializationCache = this.providersMemento.notebookSerializationProviderCache;
for (let key in serializationCache) {
if (!knownProviders.some(x => x === key)) {
this._serializationProviders.delete(key);
delete serializationCache[key];
}
}
}
private registerBuiltInProvider() {
let sqlProvider = new SqlNotebookProvider(this._instantiationService);
this.registerProvider(sqlProvider.providerId, sqlProvider);
notebookRegistry.registerNotebookProvider({
provider: sqlProvider.providerId,
private registerBuiltInProviders() {
let serializationProvider = new SqlSerializationProvider(this._instantiationService);
this.registerSerializationProvider(serializationProvider.providerId, serializationProvider);
let executeProvider = new SqlExecuteProvider(this._instantiationService);
this.registerExecuteProvider(executeProvider.providerId, executeProvider);
notebookRegistry.registerProviderDescription({
provider: serializationProvider.providerId,
fileExtensions: DEFAULT_NOTEBOOK_FILETYPE,
standardKernels: { name: notebookConstants.SQL, displayName: notebookConstants.SQL, connectionProviderIds: [notebookConstants.SQL_CONNECTION_PROVIDER] }
});
}
protected async removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService): Promise<void> {
const notebookProvider = 'notebookProvider';
try {
const extensionDescriptions = await extensionService.getExtensions();
let extensionDescription = extensionDescriptions.find(c => c.identifier.value.toLowerCase() === identifier.id.toLowerCase());
if (extensionDescription && extensionDescription.contributes
&& extensionDescription.contributes[notebookProvider] //'notebookProvider' isn't a field defined on IExtensionContributions so contributes[notebookProvider] is 'any'. TODO: Code cleanup
&& extensionDescription.contributes[notebookProvider].providerId) {
let id = extensionDescription.contributes[notebookProvider].providerId;
delete this.providersMemento.notebookProviderCache[id];
&& extensionDescription.contributes[Extensions.NotebookProviderDescriptionContribution]
&& extensionDescription.contributes[Extensions.NotebookProviderDescriptionContribution].providerId) {
let id = extensionDescription.contributes[Extensions.NotebookProviderDescriptionContribution].providerId;
delete this.providersMemento.notebookSerializationProviderCache[id];
delete this.providersMemento.notebookExecuteProviderCache[id];
}
} catch (err) {
onUnexpectedError(err);

View File

@@ -7,15 +7,12 @@ import { nb } from 'azdata';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
import { SqlSessionManager } from 'sql/workbench/services/notebook/browser/sql/sqlSessionManager';
export class SqlNotebookManager implements nb.NotebookManager {
private _contentManager: nb.ContentManager;
export class SqlExecuteManager implements nb.ExecuteManager {
private _sessionManager: nb.SessionManager;
constructor(instantiationService: IInstantiationService) {
this._contentManager = instantiationService.createInstance(LocalContentManager);
this._sessionManager = new SqlSessionManager(instantiationService);
}
@@ -23,10 +20,6 @@ export class SqlNotebookManager implements nb.NotebookManager {
return SQL_NOTEBOOK_PROVIDER;
}
public get contentManager(): nb.ContentManager {
return this._contentManager;
}
public get serverManager(): nb.ServerManager | undefined {
return undefined;
}
@@ -34,8 +27,4 @@ export class SqlNotebookManager implements nb.NotebookManager {
public get sessionManager(): nb.SessionManager {
return this._sessionManager;
}
public get standardKernels(): nb.IStandardKernel[] {
return [];
}
}

View File

@@ -6,21 +6,21 @@
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotebookManager, INotebookProvider, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { SqlNotebookManager } from 'sql/workbench/services/notebook/browser/sql/sqlNotebookManager';
import { IExecuteManager, IExecuteProvider, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { SqlExecuteManager } from 'sql/workbench/services/notebook/browser/sql/sqlExecuteManager';
export class SqlNotebookProvider implements INotebookProvider {
private manager: SqlNotebookManager;
export class SqlExecuteProvider implements IExecuteProvider {
private manager: SqlExecuteManager;
constructor(private _instantiationService: IInstantiationService) {
this.manager = new SqlNotebookManager(this._instantiationService);
constructor(instantiationService: IInstantiationService) {
this.manager = new SqlExecuteManager(instantiationService);
}
public get providerId(): string {
return SQL_NOTEBOOK_PROVIDER;
}
getNotebookManager(notebookUri: URI): Thenable<INotebookManager> {
getExecuteManager(notebookUri: URI): Thenable<IExecuteManager> {
return Promise.resolve(this.manager);
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb } from 'azdata';
import { ISerializationManager, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class SqlSerializationManager implements ISerializationManager {
private _manager: LocalContentManager;
constructor(instantiationService: IInstantiationService) {
this._manager = instantiationService.createInstance(LocalContentManager);
}
public get providerId(): string {
return SQL_NOTEBOOK_PROVIDER;
}
public get contentManager(): nb.ContentManager {
return this._manager;
}
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { ISerializationManager, ISerializationProvider, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/notebook/browser/notebookService';
import { SqlSerializationManager } from 'sql/workbench/services/notebook/browser/sql/sqlSerializationManager';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class SqlSerializationProvider implements ISerializationProvider {
private _manager: SqlSerializationManager;
constructor(instantiationService: IInstantiationService) {
this._manager = new SqlSerializationManager(instantiationService);
}
public get providerId(): string {
return SQL_NOTEBOOK_PROVIDER;
}
getSerializationManager(notebookUri: URI): Thenable<ISerializationManager> {
return Promise.resolve(this._manager);
}
}