pma-cli 0.1.3

phpmyadmin cli written in Rust
Documentation
use crate::{
    command::ExportOption,
    pma_client::{PMAClient, PMAExportConfig, Table},
    read_zip::read_zip,
};
use anyhow::bail;
use itertools::Itertools;
use regex::Regex;
use std::path::Path;
use tokio::{fs::File, io::AsyncWriteExt};

pub async fn export(
    client: PMAClient,
    tables: Vec<String>,
    option: ExportOption,
) -> anyhow::Result<()> {
    let actual_tables = client.fetch_tables(&option.db).await?;
    check_if_table_exists(&actual_tables, &tables)?;
    _export(client, tables, option).await?;
    Ok(())
}

pub async fn export_all(
    client: PMAClient,
    filter_table: Option<String>,
    option: ExportOption,
) -> anyhow::Result<()> {
    let filter = filter_table.map(|f| Regex::new(&f)).transpose()?;
    let tables = client
        .fetch_tables(&option.db)
        .await?
        .into_iter()
        .filter(|table| filter.as_ref().map(|f| f.is_match(table)).unwrap_or(true))
        .collect();
    _export(client, tables, option).await?;
    Ok(())
}

async fn _export(
    client: PMAClient,
    tables: Vec<String>,
    option: ExportOption,
) -> anyhow::Result<()> {
    let config = opt_to_config(tables, option).await?;
    let ignore_database_file = config.pma.separate_files && !config.pma.create_database;
    let (token, cookie) = client.fetch_token().await?;
    let exported_bin = client.export(&token, &cookie, config.pma).await?;
    let files = read_zip(exported_bin).await?;
    let create_files = files
        .into_iter()
        .filter(|f| {
            if ignore_database_file && f.header.file_name.contains("database") {
                return false;
            }
            true
        })
        .map(|f| {
            println!("Exported: {}", f.header.file_name);
            let path = config.output_path.clone();
            tokio::spawn(async move {
                let root = Path::new(&path).to_path_buf();
                let path = root.clone().join(f.header.file_name);
                let mut file = File::create(path).await?;
                file.write_all(&f.content).await?;
                anyhow::Ok(())
            })
        });
    for h in create_files {
        h.await??;
    }

    Ok(())
}

fn check_if_table_exists(actual: &[String], check_tables: &[String]) -> anyhow::Result<()> {
    let invalid_tables = check_tables
        .iter()
        .filter(|table| !actual.contains(table))
        .collect::<Vec<_>>();
    if !invalid_tables.is_empty() {
        let tables = invalid_tables.iter().join(",");
        bail!("Not Found table. {}", tables);
    }
    Ok(())
}

#[derive(Debug)]
struct ExportConfig {
    pma: PMAExportConfig,
    output_path: String,
}

async fn opt_to_config(tables: Vec<String>, option: ExportOption) -> anyhow::Result<ExportConfig> {
    let ExportOption {
        db,
        all_data,
        ref data,
        ref data_prefix,
        separate_files,
        output,
        create_database,
    } = option;
    if let Some(d) = data {
        check_if_table_exists(&tables, d)?;
    }
    let tables = tables
        .into_iter()
        .map(|t| {
            let data = match (all_data, data, data_prefix) {
                (true, _, _) => true,
                (_, Some(data), _) => data.contains(&t),
                (_, _, Some(prefix)) => t.starts_with(prefix),
                _ => false,
            };
            Table {
                name: t,
                structure: true,
                data,
            }
        })
        .collect();
    Ok(ExportConfig {
        pma: PMAExportConfig {
            db,
            tables,
            separate_files,
            create_database,
        },
        output_path: output,
    })
}