use clap::{Parser, Subcommand}; use rand::seq::IteratorRandom; use std::io; use std::time::Instant; use std::{fs, sync::mpsc, thread}; use chgk_ledb_lib::db; use chgk_ledb_lib::questions; use chgk_ledb_lib::source; use crate::questions::{Question, QuestionsConverter}; use crate::source::ReadSourceQuestionsBatches; use chgk_ledb_lib::util::ErrorToString; const ZIP_FILENAME: &str = "json.zip"; const NEW_DB_FILENAME: &str = "db.dat"; #[derive(Subcommand, Debug)] enum Command { Write, Print { #[clap(value_parser, default_value = "0")] id: u32, }, ZipPrint { #[clap(value_parser, default_value = "0")] file_num: usize, #[clap(value_parser, default_value = "0")] num: usize, }, } #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] struct Cli { #[clap(subcommand)] command: Command, #[clap(short, long, action)] measure: bool, } fn main() { let args = Cli::parse(); let mut action: Box = match &args.command { Command::Write => Box::new(write_db), Command::Print { id } => { let get_question = Box::new(|| read_from_db(*id)); Box::new(|| print_question_from(get_question)) } Command::ZipPrint { file_num, num } => { let get_question = Box::new(|| read_from_zip(*file_num, *num)); Box::new(|| print_question_from(get_question)) } }; if args.measure { action = Box::new(|| measure_and_print(action)); } action(); } // measure and return time elapsed in `func` in seconds pub fn measure(func: F) -> f64 { let start = Instant::now(); func(); let elapsed = start.elapsed(); (elapsed.as_secs() as f64) + (elapsed.subsec_nanos() as f64 / 1_000_000_000.0) } pub fn measure_and_print(func: F) { let m = measure(func); eprintln!("{}", m); } fn print_question_from(get_q: F) where F: FnOnce() -> Result, { let q = get_q().expect("question not found"); println!("{:#?}", q) } fn read_from_zip(file_num: usize, mut num: usize) -> Result { let mut rng = rand::thread_rng(); let zip_file = fs::File::open(ZIP_FILENAME).str_err()?; let zip_reader = io::BufReader::new(zip_file); let archive = zip::ZipArchive::new(zip_reader).str_err()?; let mut source_questions = archive.source_questions(); let (filename, batch) = if file_num == 0 { source_questions .choose(&mut rng) .ok_or("rand choose".to_string())? } else { source_questions .nth(file_num - 1) .ok_or(format!("file nth #{file_num} => None"))? }; let mut batch = batch.map_err(|e| format!("get batch from file #{file_num} => {e}"))?; batch.filename = filename; let questions: Vec = batch.into(); if num == 0 { num = (1..=questions.len()) .choose(&mut rng) .ok_or("rand choose".to_string())?; } Ok(questions[num - 1].clone()) } fn read_from_db(id: u32) -> Result { let reader: db::Reader = db::Reader::new(NEW_DB_FILENAME, 2048)?; let len = reader.len(); let mut questions = reader.into_iter(); let question = match id { 0 => { let mut rng = rand::thread_rng(); questions .choose(&mut rng) .ok_or(format!("rand choose, len = {len}"))? } _ => questions .nth((id - 1) as usize) .ok_or(format!("get nth #{id} => None"))?, }; Ok(question) } fn write_db() { let (tx, rx) = mpsc::channel::(); [ thread::spawn(move || zip_reader_task(tx)), thread::spawn(move || db_writer_task(rx)), ] .into_iter() .for_each(|handle| handle.join().expect("thread panic")); println!("all done"); } fn zip_reader_task(tx: mpsc::Sender) { let zip_file = fs::File::open(ZIP_FILENAME).unwrap(); let zip_reader = io::BufReader::new(zip_file); let archive = zip::ZipArchive::new(zip_reader).unwrap(); let mut source_questions = archive.source_questions(); let questions = source_questions .convert() .enumerate() .map(|(num, mut question)| { question.num = 1 + num as u32; question }); for question in questions { let res = tx.send(question); if res.is_err() { break; } } println!("read done"); } fn db_writer_task(rx: mpsc::Receiver) { let writer_opts = db::WriterOpts::default(); let mut writer: db::Writer = db::Writer::new(NEW_DB_FILENAME, writer_opts).expect("new db writer"); writer .load(&mut rx.iter()) .unwrap_or_else(|e| panic!("db writer load, {e:#?}")); writer.finish().expect("db writer finish"); println!("write done"); }