asimov-dataset-cli 25.0.0-dev.6

ASIMOV Dataset Command-Line Interface (CLI)
Documentation
// This is free and unencumbered software released into the public domain.

use borsh::BorshSerialize;
use color_eyre::{
    eyre::{eyre, Context as _, Result},
    Section,
};
use crossbeam::channel::Sender;
use near_api::{
    near_primitives::{
        action::{Action, DeployContractAction, FunctionCallAction},
        errors::{
            ActionError, ActionErrorKind, CompilationError, FunctionCallError, TxExecutionError,
        },
        views::FinalExecutionStatus,
    },
    AccountId, NearGas, NetworkConfig, Transaction,
};
use std::{io::Read, path::PathBuf, sync::Arc};

use crate::context::Context;

#[derive(Clone, Debug)]
pub struct PublishStatsReport {
    pub tx: Sender<crate::ui::Event>,
}

/// Splits the files into (prepared, unprepared) according to their file extension.
pub fn split_prepared_files(files: &[PathBuf]) -> (Vec<PathBuf>, Vec<PathBuf>) {
    files
        .iter()
        .cloned()
        .partition(|file| file.extension().is_some_and(|ext| ext == "rdfb"))
}

pub async fn upload_repository_contract(
    repository: AccountId,
    signer_id: AccountId,
    signer: Arc<near_api::Signer>,
    network: &NetworkConfig,
) -> Result<()> {
    let code = include_bytes!("../assets/log_vault.wasm").to_vec();
    let tx_outcome = Transaction::construct(signer_id.clone(), repository.clone())
        .add_action(Action::DeployContract(DeployContractAction { code }))
        .with_signer(signer)
        .send_to(network)
        .await
        .context("Failed to send DeployContract tx to RPC")?;

    use near_api::near_primitives::views::FinalExecutionStatus;
    match tx_outcome.status {
        FinalExecutionStatus::NotStarted => todo!(),
        FinalExecutionStatus::Started => todo!(),
        FinalExecutionStatus::SuccessValue(_items) => Ok(()),
        FinalExecutionStatus::Failure(error) => Err(eyre!(error)),
    }
}

#[derive(derive_builder::Builder)]
#[builder(pattern = "owned")]
pub struct Params<I> {
    signer_id: AccountId,
    signer: Arc<near_api::Signer>,
    repository: AccountId,
    #[builder(setter(into), default)]
    dataset: Option<String>,
    network: NetworkConfig,
    files: I,
    #[builder(setter(into, strip_option), default)]
    report: Option<PublishStatsReport>,
}

impl<I> Params<I> {
    pub fn new(
        repository: AccountId,
        signer_id: AccountId,
        dataset: Option<String>,
        signer: Arc<near_api::Signer>,
        network: NetworkConfig,
        files: I,
        report: Option<PublishStatsReport>,
    ) -> Self {
        Self {
            repository,
            signer_id,
            dataset,
            signer,
            network,
            files,
            report,
        }
    }
}

pub async fn publish_datasets<I>(ctx: Context, params: Params<I>) -> Result<()>
where
    I: Iterator<Item = (PathBuf, usize)>,
{
    let dataset = params.dataset.unwrap_or(String::from(""));
    for (filename, statement_count) in params.files {
        if ctx.is_cancelled() {
            break;
        }
        let mut args = Vec::new();
        1_u8.serialize(&mut args)?; // version 1
        dataset.serialize(&mut args)?;
        1_u8.serialize(&mut args)?; // RDF/Borsh dataset encoding

        let bytes = std::fs::File::open(&filename)?.read_to_end(&mut args)?;

        let tx_outcome = Transaction::construct(params.signer_id.clone(), params.repository.clone())
            .add_action(Action::FunctionCall(Box::new(FunctionCallAction {
                method_name: "rdf_insert".into(),
                args,
                gas: NearGas::from_tgas(300).as_gas(),
                deposit: 0,
            })))
            .with_signer(params.signer.clone())
            .send_to(&params.network)
            .await
            .inspect(
                |outcome| tracing::info!(?filename, status = ?outcome.transaction_outcome.outcome.status, "uploaded dataset"),
            )?;

        if let FinalExecutionStatus::Failure(error) = tx_outcome.status {
            let msg = format!("Failed to upload batch: {}", filename.display());

            if matches!(
                error,
                TxExecutionError::ActionError(ActionError {
                    kind: ActionErrorKind::FunctionCallError(FunctionCallError::CompilationError(
                        CompilationError::CodeDoesNotExist { account_id: _ }
                    )),
                    ..
                })
            ) {
                return Err(error)
                    .wrap_err(msg)
                    .with_note(|| "The address does not contain a contract with a method `rdf_insert`")
                    .with_suggestion(|| "If you want to upload a basic vault at the address you can rerun the publish command with the option `--upload-contract`");
            }

            return Err(error).wrap_err(msg);
        }

        std::fs::remove_file(&filename).ok();

        if let Some(ref report) = params.report {
            report
                .tx
                .send(crate::ui::Event::Publish(crate::ui::PublishProgress {
                    filename,
                    bytes,
                    statement_count,
                }))
                .ok();
        }
    }
    Ok(())
}