"use strict";
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TerminalManager = void 0;
const algorithm_1 = require("@lumino/algorithm");
const polling_1 = require("@lumino/polling");
const signaling_1 = require("@lumino/signaling");
const __1 = require("..");
const basemanager_1 = require("../basemanager");
const restapi_1 = require("./restapi");
const default_1 = require("./default");
/**
 * A terminal session manager.
 */
class TerminalManager extends basemanager_1.BaseManager {
    /**
     * Construct a new terminal manager.
     */
    constructor(options = {}) {
        var _a;
        super(options);
        this._isReady = false;
        // As an optimization, we unwrap the models to just store the names.
        this._names = [];
        this._terminalConnections = new Set();
        this._runningChanged = new signaling_1.Signal(this);
        this._connectionFailure = new signaling_1.Signal(this);
        // Check if terminals are available
        if (!this.isAvailable()) {
            this._ready = Promise.reject('Terminals unavailable');
            this._ready.catch(_ => undefined);
            return;
        }
        // Start polling with exponential backoff.
        this._pollModels = new polling_1.Poll({
            auto: false,
            factory: () => this.requestRunning(),
            frequency: {
                interval: 10 * 1000,
                backoff: true,
                max: 300 * 1000
            },
            name: `@jupyterlab/services:TerminalManager#models`,
            standby: (_a = options.standby) !== null && _a !== void 0 ? _a : 'when-hidden'
        });
        // Initialize internal data.
        this._ready = (async () => {
            await this._pollModels.start();
            await this._pollModels.tick;
            this._isReady = true;
        })();
    }
    /**
     * Test whether the manager is ready.
     */
    get isReady() {
        return this._isReady;
    }
    /**
     * A promise that fulfills when the manager is ready.
     */
    get ready() {
        return this._ready;
    }
    /**
     * A signal emitted when the running terminals change.
     */
    get runningChanged() {
        return this._runningChanged;
    }
    /**
     * A signal emitted when there is a connection failure.
     */
    get connectionFailure() {
        return this._connectionFailure;
    }
    /**
     * Dispose of the resources used by the manager.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        this._names.length = 0;
        this._terminalConnections.forEach(x => x.dispose());
        this._pollModels.dispose();
        super.dispose();
    }
    /**
     * Whether the terminal service is available.
     */
    isAvailable() {
        return restapi_1.isAvailable();
    }
    /*
     * Connect to a running terminal.
     *
     * @param options - The options used to connect to the terminal.
     *
     * @returns The new terminal connection instance.
     *
     * #### Notes
     * The manager `serverSettings` will be used.
     */
    connectTo(options) {
        const terminalConnection = new default_1.TerminalConnection(Object.assign(Object.assign({}, options), { serverSettings: this.serverSettings }));
        this._onStarted(terminalConnection);
        if (!this._names.includes(options.model.name)) {
            // We trust the user to connect to an existing session, but we verify
            // asynchronously.
            void this.refreshRunning().catch(() => {
                /* no-op */
            });
        }
        return terminalConnection;
    }
    /**
     * Create an iterator over the most recent running terminals.
     *
     * @returns A new iterator over the running terminals.
     */
    running() {
        return algorithm_1.iter(this._models);
    }
    /**
     * Force a refresh of the running terminals.
     *
     * @returns A promise that with the list of running terminals.
     *
     * #### Notes
     * This is intended to be called only in response to a user action,
     * since the manager maintains its internal state.
     */
    async refreshRunning() {
        await this._pollModels.refresh();
        await this._pollModels.tick;
    }
    /**
     * Create a new terminal session.
     *
     * @param options - The options used to create the terminal.
     *
     * @returns A promise that resolves with the terminal connection instance.
     *
     * #### Notes
     * The manager `serverSettings` will be used unless overridden in the
     * options.
     */
    async startNew(options) {
        const model = await restapi_1.startNew(this.serverSettings, options === null || options === void 0 ? void 0 : options.name, options === null || options === void 0 ? void 0 : options.cwd);
        await this.refreshRunning();
        return this.connectTo({ model });
    }
    /**
     * Shut down a terminal session by name.
     */
    async shutdown(name) {
        await restapi_1.shutdownTerminal(name, this.serverSettings);
        await this.refreshRunning();
    }
    /**
     * Shut down all terminal sessions.
     *
     * @returns A promise that resolves when all of the sessions are shut down.
     */
    async shutdownAll() {
        // Update the list of models to make sure our list is current.
        await this.refreshRunning();
        // Shut down all models.
        await Promise.all(this._names.map(name => restapi_1.shutdownTerminal(name, this.serverSettings)));
        // Update the list of models to clear out our state.
        await this.refreshRunning();
    }
    /**
     * Execute a request to the server to poll running terminals and update state.
     */
    async requestRunning() {
        var _a, _b;
        let models;
        try {
            models = await restapi_1.listRunning(this.serverSettings);
        }
        catch (err) {
            // Handle network errors, as well as cases where we are on a
            // JupyterHub and the server is not running. JupyterHub returns a
            // 503 (<2.0) or 424 (>2.0) in that case.
            if (err instanceof __1.ServerConnection.NetworkError ||
                ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 503 ||
                ((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 424) {
                this._connectionFailure.emit(err);
            }
            throw err;
        }
        if (this.isDisposed) {
            return;
        }
        const names = models.map(({ name }) => name).sort();
        if (names === this._names) {
            // Identical models list, so just return
            return;
        }
        this._names = names;
        this._terminalConnections.forEach(tc => {
            if (!names.includes(tc.name)) {
                tc.dispose();
            }
        });
        this._runningChanged.emit(this._models);
    }
    /**
     * Handle a session starting.
     */
    _onStarted(terminalConnection) {
        this._terminalConnections.add(terminalConnection);
        terminalConnection.disposed.connect(this._onDisposed, this);
    }
    /**
     * Handle a session terminating.
     */
    _onDisposed(terminalConnection) {
        this._terminalConnections.delete(terminalConnection);
        // Update the running models to make sure we reflect the server state
        void this.refreshRunning().catch(() => {
            /* no-op */
        });
    }
    get _models() {
        return this._names.map(name => {
            return { name };
        });
    }
}
exports.TerminalManager = TerminalManager;
//# sourceMappingURL=manager.js.map