"use strict";
/*
 *  This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.
 *  License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withCustomizedAndSoftwareSpec = void 0;
const relative_time_1 = require("@cocalc/util/relative-time");
const hof_1 = require("async-await-utils/hof");
const compute_components_json_1 = __importDefault(require("dist/inventory/compute-components.json"));
const compute_inventory_json_1 = __importDefault(require("dist/inventory/compute-inventory.json"));
const with_customize_1 = __importDefault(require("lib/with-customize"));
const path_1 = require("path");
const fs_1 = require("fs");
const { readFile } = fs_1.promises;
async function file2json(path) {
    const data = await readFile(path, "utf8");
    return JSON.parse(data);
}
// load the current version of the software specs – if there is a problem, use the locally stored files as fallback.
// both files go hand-in-hand, hence either both work or both are the fallback!
async function fetchInventory() {
    // for development, set the env variable to directory, where this files are
    const localSpec = process.env.COCALC_COMPUTE_ENV_SPEC;
    if (localSpec != null) {
        // read compute-inventory.json and compute-components.json from the local filesystem
        console.log(`reading inventory information from directory ${localSpec}`);
        return {
            inventory: await file2json(`${localSpec}/compute-inventory.json`),
            components: await file2json(`${localSpec}/compute-components.json`),
        };
    }
    const urlI = "https://storage.googleapis.com/cocalc-compute-environment/compute-inventory.json";
    const urlC = "https://storage.googleapis.com/cocalc-compute-environment/compute-components.json";
    try {
        const [respI, respC] = await Promise.all([fetch(urlI), fetch(urlC)]);
        if (respI.ok && respC.ok) {
            const inventory = await respI.json();
            const components = await respC.json();
            console.log(`successfully fetched and loaded software environment config files`);
            return { inventory, components };
        }
        else {
            console.log(`Error fetching inventory: ${respI.status}: ${respI.statusText}.`);
            throw Error("problem");
        }
    }
    catch (err) {
        console.log(`Error fetching inventory: ${err} -- using fallback.`);
        return {
            inventory: compute_inventory_json_1.default,
            components: compute_components_json_1.default,
        };
    }
}
// cached instance
let SPEC;
let SoftwareEnvSpecs;
let SoftwareEnvSpecsTimestamp = 0;
const fetchSoftwareSpec = (0, hof_1.reuseInFlight)(async function () {
    const { inventory, components } = await fetchInventory();
    SoftwareEnvSpecs = { inventory, components };
    SoftwareEnvSpecsTimestamp = Date.now();
});
async function getSoftwareInfo() {
    // if SoftwareEnvSpecs is not set or not older than one hour, fetch it
    if (SoftwareEnvSpecs != null) {
        if (SoftwareEnvSpecsTimestamp > (0, relative_time_1.hours_ago)(1).getTime()) {
            // fresh enough, just return it
            return SoftwareEnvSpecs;
        }
        else {
            // we asynchroneously fetch to refresh, but return the data immediately to the client
            fetchSoftwareSpec();
            return SoftwareEnvSpecs;
        }
    }
    else {
        await fetchSoftwareSpec();
        return SoftwareEnvSpecs;
    }
}
async function getSoftwareInfoLang(lang) {
    const { inventory, components } = await getSoftwareInfo();
    return { inventory: inventory[lang], components: components[lang] };
}
// during startup, we fetch getSoftwareSpec() once to fill the cache…
(async function () {
    fetchSoftwareSpec(); // not blocking
})();
async function getSoftwareSpec() {
    if (SPEC != null)
        return SPEC;
    const nextSpec = {};
    const { inventory } = await getSoftwareInfo();
    for (const cmd in inventory.language_exes) {
        const info = inventory.language_exes[cmd];
        if (nextSpec[info.lang] == null) {
            nextSpec[info.lang] = {};
        }
        // the basename of the cmd path
        const base = cmd.indexOf(" ") > 0 ? cmd : (0, path_1.basename)(cmd);
        nextSpec[info.lang][base] = {
            cmd,
            name: info.name,
            doc: info.doc,
            url: info.url,
        };
    }
    SPEC = nextSpec;
    return SPEC;
}
// this is for the server side getServerSideProps function
async function withCustomizedAndSoftwareSpec(context, lang, executableInfo) {
    const [customize, spec] = await Promise.all([
        (0, with_customize_1.default)({ context }),
        getSoftwareSpec(),
    ]);
    if (lang === "executables") {
        // this is instant because specs are already in the cache
        const { inventory } = await getSoftwareInfo();
        customize.props.executablesSpec = inventory.executables;
        return customize;
    }
    else {
        customize.props.spec = spec[lang];
        // this is instant because specs are already in the cache
        const { inventory, components } = await getSoftwareInfoLang(lang);
        customize.props.inventory = inventory;
        customize.props.components = components;
    }
    if (executableInfo != null) {
        const { inventory } = await getSoftwareInfo();
        customize.props.execInfo = {};
        for (const cmd of executableInfo) {
            customize.props.execInfo[cmd] = inventory.executables?.[cmd] ?? null;
        }
    }
    return customize;
}
exports.withCustomizedAndSoftwareSpec = withCustomizedAndSoftwareSpec;
//# sourceMappingURL=software-specs.js.map