/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import { nb } from 'azdata'; import * as vscode from 'vscode'; import * as path from 'path'; import { ServerConnection } from '@jupyterlab/services'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { ApiWrapper } from '../common/apiWrapper'; import { JupyterServerInstallation } from './jupyterServerInstallation'; import * as utils from '../common/utils'; import { IServerInstance } from './common'; import { PerFolderServerInstance, IInstanceOptions } from './serverInstance'; import { CommandContext } from '../common/constants'; export interface IServerManagerOptions { documentPath: string; jupyterInstallation: JupyterServerInstallation; extensionContext: vscode.ExtensionContext; apiWrapper?: ApiWrapper; factory?: ServerInstanceFactory; } export class LocalJupyterServerManager implements nb.ServerManager, vscode.Disposable { private _serverSettings: Partial; private _onServerStarted = new vscode.EventEmitter(); private _instanceOptions: IInstanceOptions; private _apiWrapper: ApiWrapper; private _jupyterServer: IServerInstance; factory: ServerInstanceFactory; constructor(private options: IServerManagerOptions) { this._apiWrapper = options.apiWrapper || new ApiWrapper(); this.factory = options.factory || new ServerInstanceFactory(); } public get serverSettings(): Partial { return this._serverSettings; } public get isStarted(): boolean { return !!this._jupyterServer; } public get instanceOptions(): IInstanceOptions { return this._instanceOptions; } public get onServerStarted(): vscode.Event { return this._onServerStarted.event; } public get jupyterServerInstallation(): JupyterServerInstallation | undefined { return this.options && this.options.jupyterInstallation; } public async startServer(): Promise { try { if (!this._jupyterServer) { this._jupyterServer = await this.doStartServer(); this.options.extensionContext.subscriptions.push(this); let partialSettings = LocalJupyterServerManager.getLocalConnectionSettings(this._jupyterServer.uri); this._serverSettings = partialSettings; this._onServerStarted.fire(); } } catch (error) { // this is caught and notified up the stack, no longer showing a message here throw error; } } public dispose(): void { this.stopServer().catch(err => { let msg = utils.getErrorMessage(err); this._apiWrapper.showErrorMessage(localize('shutdownError', 'Shutdown of Notebook server failed: {0}', msg)); }); } public async stopServer(): Promise { if (this._jupyterServer) { await this._jupyterServer.stop(); } } public static getLocalConnectionSettings(uri: vscode.Uri): Partial { return { baseUrl: `${uri.scheme}://${uri.authority}`, token: LocalJupyterServerManager.getToken(uri.query) }; } private static getToken(query: string): string { if (query) { let parts = query.split('='); if (parts && parts.length >= 2) { return parts[1]; } } return ''; } private get documentPath(): string { return this.options.documentPath; } private async doStartServer(): Promise { // We can't find or create servers until the installation is complete let installation = this.options.jupyterInstallation; await installation.promptForPythonInstall(); await installation.promptForPackageUpgrade(); this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true); // Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the // notebook to open. This will be the workspace folder if the notebook uri is inside a workspace // folder. Otherwise, it will be the folder that the notebook is inside. Ultimately, this means // a new notebook server will be started for each folder a notebook is opened from. // // eg, opening: // /path1/nb1.ipynb // /path2/nb2.ipynb // /path2/nb3.ipynb // ... will result in 2 notebook servers being started, one for /path1/ and one for /path2/ let notebookDir = this._apiWrapper.getWorkspacePathFromUri(vscode.Uri.file(this.documentPath)); if (!notebookDir) { let docDir; // If a folder is passed in for documentPath, use the folder instead of calling dirname docDir = path.extname(this.documentPath) ? path.dirname(this.documentPath) : this.documentPath; if (docDir === '.') { // If the user is using a system version of python, then // '.' will try to create a notebook in a system directory. // Since this will fail due to permissions, use the user's // home folder instead. notebookDir = utils.getUserHome(); } else { notebookDir = docDir; } } // TODO handle notification of start/stop status // notebookContext.updateLoadingMessage(localizedConstants.msgJupyterStarting); // TODO refactor server instance so it doesn't need the kernel. Likely need to reimplement this // for notebook version let serverInstanceOptions: IInstanceOptions = { documentPath: this.documentPath, notebookDirectory: notebookDir, install: installation }; this._instanceOptions = serverInstanceOptions; let server = this.factory.createInstance(serverInstanceOptions); await server.configure(); await server.start(); return server; } } export class ServerInstanceFactory { createInstance(options: IInstanceOptions): IServerInstance { return new PerFolderServerInstance(options); } }