#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable import/first */
const os_1 = require("os");
const path_1 = __importDefault(require("path"));
const node_cluster_1 = __importDefault(require("node:cluster"));
process.env.NODE_CONFIG_DIR = process.env.SINGULARITY_PATH || path_1.default.join((0, os_1.homedir)(), '.singularity');
const commander_1 = require("commander");
const package_json_1 = __importDefault(require("../package.json"));
const Datastore_1 = __importDefault(require("./common/Datastore"));
const DealPreparationService_1 = __importDefault(require("./deal-preparation/DealPreparationService"));
const DealPreparationWorker_1 = __importDefault(require("./deal-preparation/DealPreparationWorker"));
const axios_1 = __importDefault(require("axios"));
const config_1 = __importDefault(require("config"));
const cli_util_1 = __importDefault(require("./cli-util"));
const IndexService_1 = __importDefault(require("./index/IndexService"));
const HttpHostingService_1 = __importDefault(require("./hosting/HttpHostingService"));
const DealTrackingService_1 = __importDefault(require("./deal-tracking/DealTrackingService"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const Logger_1 = __importStar(require("./common/Logger"));
const IpfsCore = __importStar(require("ipfs-core"));
const logger = Logger_1.default.getLogger(Logger_1.Category.Cli);
const version = package_json_1.default.version;
const program = new commander_1.Command();
program.name('singularity')
    .version(version)
    .description('A tool for large-scale clients with PB-scale data onboarding to Filecoin network\nVisit https://github.com/tech-greedy/singularity for more details');
program.command('init')
    .description('Initialize the configuration directory in SINGULARITY_PATH\nIf unset, it will be initialized at HOME_DIR/.singularity')
    .action(async () => {
    const configDir = config_1.default.util.getEnv('NODE_CONFIG_DIR');
    await fs_extra_1.default.mkdirp(configDir);
    if (!await fs_extra_1.default.pathExists(path_1.default.join(configDir, 'default.toml'))) {
        logger.info(`Initializing at ${configDir} ...`);
        await fs_extra_1.default.copyFile(path_1.default.join(__dirname, '../config/default.toml'), path_1.default.join(configDir, 'default.toml'));
        logger.info(`Please check ${path_1.default.join(configDir, 'default.toml')}`);
    }
    else {
        logger.warn(`${configDir} already has the repo.`);
    }
});
program.command('daemon')
    .description('Start a daemon process for deal preparation and deal making')
    .action((_options) => {
    (async function () {
        if (node_cluster_1.default.isMaster) {
            let indexService;
            process.on('SIGUSR2', async () => {
                // Gracefully turn off mongodb memory server
                if (Datastore_1.default['mongoMemoryServer']) {
                    await Datastore_1.default['mongoMemoryServer'].stop();
                }
                // unlock ipfs repo
                if (config_1.default.get('index_service.enabled') && config_1.default.get('index_service.start_ipfs') && indexService && indexService['ipfsClient']) {
                    await indexService['ipfsClient'].stop();
                }
                process.kill(process.pid, 'SIGKILL');
            });
            await Datastore_1.default.init(false);
            await Datastore_1.default.connect();
            if (config_1.default.get('ipfs.enabled')) {
                await IpfsCore.create();
            }
            if (config_1.default.get('deal_preparation_worker.enable_cleanup')) {
                const outDir = path_1.default.resolve(process.env.NODE_CONFIG_DIR, config_1.default.get('deal_preparation_worker.out_dir'));
                if (await fs_extra_1.default.pathExists(outDir)) {
                    for (const file of await fs_extra_1.default.readdir(outDir)) {
                        const regex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.car$/;
                        if (regex.test(file)) {
                            const fullPath = path_1.default.join(outDir, file);
                            logger.info(`Removing temporary file ${fullPath}`);
                            await fs_extra_1.default.remove(fullPath);
                        }
                    }
                }
            }
            const workers = [];
            let readied = 0;
            node_cluster_1.default.on('message', _ => {
                readied += 1;
                if (readied === workers.length) {
                    for (const w of workers) {
                        w[0].send(w[1]);
                    }
                }
            });
            if (config_1.default.get('deal_preparation_service.enabled')) {
                workers.push([node_cluster_1.default.fork(), 'deal_preparation_service']);
            }
            if (config_1.default.get('deal_preparation_worker.enabled')) {
                const numWorkers = config_1.default.has('deal_preparation_worker.num_workers') ? config_1.default.get('deal_preparation_worker.num_workers') : 1;
                for (let i = 0; i < numWorkers; ++i) {
                    workers.push([node_cluster_1.default.fork(), 'deal_preparation_worker']);
                }
            }
            if (config_1.default.get('index_service.enabled')) {
                workers.push([node_cluster_1.default.fork(), 'index_service']);
            }
            if (config_1.default.get('http_hosting_service.enabled')) {
                workers.push([node_cluster_1.default.fork(), 'http_hosting_service']);
            }
            if (config_1.default.get('deal_tracking_service.enabled')) {
                workers.push([node_cluster_1.default.fork(), 'deal_tracking_service']);
            }
        }
        else if (node_cluster_1.default.isWorker) {
            await Datastore_1.default.connect();
            process.on('message', async (msg) => {
                switch (msg) {
                    case 'deal_preparation_service':
                        new DealPreparationService_1.default().start();
                        break;
                    case 'deal_preparation_worker':
                        new DealPreparationWorker_1.default().start();
                        break;
                    case 'index_service':
                        new IndexService_1.default().start();
                        break;
                    case 'http_hosting_service':
                        new HttpHostingService_1.default().start();
                        break;
                    case 'deal_tracking_service':
                        new DealTrackingService_1.default().start();
                        break;
                }
            });
            process.send('ready');
        }
    })();
});
const index = program.command('index').description('Manage the dataset index which will help map the dataset path to actual piece');
index.command('create')
    .argument('<id_or_name>', 'The dataset id or name')
    .action(async (id) => {
    const url = config_1.default.get('connection.index_service');
    let response;
    try {
        response = await axios_1.default.get(`${url}/create/${id}`);
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    const cid = response.data.rootCid;
    if (response.data.warning) {
        console.warn(response.data.warning);
    }
    console.log('To publish the index to IPNS:');
    console.log(`  ipfs name publish /ipfs/${cid}`);
    console.log('To publish the index to DNSLink:');
    console.log('  Add or update the TXT record for _dnslink.your_domain.net');
    console.log(`  _dnslink.your_domain.net.  34  IN  TXT "dnslink=/ipfs/${cid}"`);
});
const preparation = program.command('preparation')
    .alias('prep')
    .description('Manage deal preparation');
preparation.command('create').description('Start deal preparation for a local dataset')
    .argument('<datasetName>', 'A unique name of the dataset')
    .argument('<datasetPath>', 'Directory path to the dataset')
    .option('-s, --deal-size <deal_size>', 'Target deal size, i.e. 32GiB', '32 GiB')
    .action(async (name, p, options) => {
    if (!await fs_extra_1.default.pathExists(p)) {
        logger.error(`Dataset path "${p}" does not exist.`);
        process.exit(1);
    }
    const dealSize = options.dealSize;
    const url = config_1.default.get('connection.deal_preparation_service');
    let response;
    try {
        response = await axios_1.default.post(`${url}/preparation`, {
            name: name,
            path: path_1.default.resolve(p),
            dealSize: dealSize
        });
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    cli_util_1.default.renderResponse(response.data, options.json);
});
preparation.command('status').description('Check the status of a deal preparation request')
    .option('--json', 'Output with JSON format')
    .argument('<dataset>', 'The dataset id or name')
    .action(async (id, options) => {
    const url = config_1.default.get('connection.deal_preparation_service');
    let response;
    try {
        response = await axios_1.default.get(`${url}/preparation/${id}`);
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    const data = response.data;
    if (options.json) {
        console.log(JSON.stringify(data, null, 2));
    }
    else {
        const { generationRequests, ...summary } = data;
        console.log('Scanning Request Summary');
        console.table([summary]);
        console.log('Corresponding Generation Requests');
        console.table(generationRequests);
    }
});
preparation.command('list').description('List all deal preparation requests')
    .option('--json', 'Output with JSON format')
    .action(async (options) => {
    const url = config_1.default.get('connection.deal_preparation_service');
    let response;
    try {
        response = await axios_1.default.get(`${url}/preparations`);
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    cli_util_1.default.renderResponse(response.data, options.json);
});
preparation.command('generation-status').description('Check the status of a single deal generation request')
    .option('--json', 'Output with JSON format')
    .option('--dataset <dataset>', 'The dataset id or name, required if looking for generation request using index')
    .argument('<generationId>', 'A unique id or index of the generation request')
    .action(async (id, options) => {
    const url = config_1.default.get('connection.deal_preparation_service');
    let response;
    try {
        response = options.dataset ? await axios_1.default.get(`${url}/generation/${options.dataset}/${id}`) : await axios_1.default.get(`${url}/generation/${id}`);
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    const data = response.data;
    if (options.json) {
        console.log(JSON.stringify(data, null, 2));
    }
    else {
        const { fileList, ...summary } = data;
        console.log('Generation Request Summary');
        console.table([summary]);
        console.log('File Lists');
        console.table(fileList);
    }
});
async function UpdateState(id, generation, action) {
    const url = config_1.default.get('connection.deal_preparation_service');
    let response;
    try {
        if (generation) {
            response = await axios_1.default.post(`${url}/preparation/${id}/${generation}`, { action });
        }
        else {
            response = await axios_1.default.post(`${url}/preparation/${id}`, { action });
        }
    }
    catch (error) {
        cli_util_1.default.renderErrorAndExit(error);
    }
    return response;
}
preparation.command('pause').description('Pause an active deal preparation request and its active deal generation requests')
    .option('--json', 'Output with JSON format')
    .argument('<dataset>', 'The dataset id or name')
    .addArgument(new commander_1.Argument('<generationId>', 'Optionally specify a generation request').argOptional())
    .action(async (id, generation, options) => {
    const response = await UpdateState(id, generation, 'pause');
    cli_util_1.default.renderResponse(response.data, options.json);
});
preparation.command('resume').description('Resume a paused deal preparation request and its paused deal generation requests')
    .option('--json', 'Output with JSON format')
    .argument('<dataset>', 'The dataset id or name')
    .addArgument(new commander_1.Argument('<generationId>', 'Optionally specify a generation request').argOptional())
    .action(async (id, generation, options) => {
    const response = await UpdateState(id, generation, 'resume');
    cli_util_1.default.renderResponse(response.data, options.json);
});
preparation.command('retry').description('Retry an errored preparation request and its errored deal generation requests')
    .option('--json', 'Output with JSON format')
    .argument('<dataset>', 'The dataset id or name')
    .addArgument(new commander_1.Argument('<generationId>', 'Optionally specify a generation request').argOptional())
    .action(async (id, generation, options) => {
    const response = await UpdateState(id, generation, 'retry');
    cli_util_1.default.renderResponse(response.data, options.json);
});
program.showSuggestionAfterError();
program.showHelpAfterError('(add --help for additional information)');
program.parse();
//# sourceMappingURL=singularity.js.map