ckb-cli 0.100.0

ckb command line interface
use std::path::PathBuf;
use std::sync::Arc;

use ckb_index::IndexDatabase;
use ckb_sdk::{GenesisInfo, HttpRpcClient};
use ckb_types::{
    core::{service::Request, BlockView},
    prelude::*,
    H256,
};
use ckb_util::RwLock;
use clap::{App, ArgMatches};

use super::{CliSubCommand, Output};
use crate::utils::index::{with_db, IndexController, IndexRequest, IndexThreadState};

pub struct IndexSubCommand<'a> {
    rpc_client: &'a mut HttpRpcClient,
    genesis_info: Option<GenesisInfo>,
    index_dir: PathBuf,
    index_controller: IndexController,
    index_state: Arc<RwLock<IndexThreadState>>,
    wait_for_sync: bool,
}

impl<'a> IndexSubCommand<'a> {
    pub fn new(
        rpc_client: &'a mut HttpRpcClient,
        genesis_info: Option<GenesisInfo>,
        index_dir: PathBuf,
        index_controller: IndexController,
        index_state: Arc<RwLock<IndexThreadState>>,
        wait_for_sync: bool,
    ) -> IndexSubCommand<'a> {
        IndexSubCommand {
            rpc_client,
            genesis_info,
            index_dir,
            index_controller,
            index_state,
            wait_for_sync,
        }
    }

    pub fn subcommand(name: &'static str) -> App<'static> {
        App::new(name)
            .about("Index database management")
            .subcommands(vec![
                App::new("db-metrics").about("Show index database metrics"),
                App::new("current-db-info")
                    .about("Show current index database's basic information"),
                App::new("rebuild-current-db").about("Remove and rebuild current index database"),
            ])
    }

    fn genesis_info(&mut self) -> Result<GenesisInfo, String> {
        if self.genesis_info.is_none() {
            let genesis_block: BlockView = self
                .rpc_client
                .get_block_by_number(0)?
                .expect("Can not get genesis block?")
                .into();
            self.genesis_info = Some(GenesisInfo::from_block(&genesis_block)?);
        }
        Ok(self.genesis_info.clone().unwrap())
    }

    fn with_db<F, T>(&mut self, func: F) -> Result<T, String>
    where
        F: FnOnce(IndexDatabase) -> T,
    {
        let genesis_info = self.genesis_info()?;
        with_db(
            func,
            self.rpc_client,
            genesis_info,
            &self.index_dir,
            self.index_controller.clone(),
            self.wait_for_sync,
        )
    }

    fn db_dir(&mut self) -> Result<PathBuf, String> {
        let genesis_info = self.genesis_info()?;
        let genesis_hash: H256 = genesis_info.header().hash().unpack();
        Ok(self.index_dir.join(format!("{:#x}", genesis_hash)))
    }
}

impl<'a> CliSubCommand for IndexSubCommand<'a> {
    fn process(&mut self, matches: &ArgMatches, _debug: bool) -> Result<Output, String> {
        match matches.subcommand() {
            ("current-db-info", _) => Ok(Output::new_output(serde_json::json!({
                "directory": self.db_dir()?.to_string_lossy(),
                "state": self.index_state.read().to_string(),
            }))),
            ("rebuild-current-db", _) => {
                Request::call(
                    self.index_controller.sender(),
                    IndexRequest::RebuildCurrentDB,
                );
                Ok(Output::new_success())
            }
            ("db-metrics", _) => {
                let metrcis = self.with_db(|db| db.get_metrics(None))?;
                let resp = serde_json::to_value(metrcis).map_err(|err| err.to_string())?;
                Ok(Output::new_output(resp))
            }
            _ => Err(Self::subcommand("index").generate_usage()),
        }
    }
}