onehis 0.3.0

All your history in one file
use std::fs;
use std::io::Write;

use crate::progress::TUICollector;
use crate::source::Source;
use crate::{database::Database, util::full_timerange};
use anyhow::{Context, Error, Result};
use log::{debug, info};

pub fn backup(history_files: Vec<String>, db_file: String, dry_run: bool) -> Result<()> {
    let (start, end) = full_timerange();
    debug!("files:{:?}, start:{}, end:{}", history_files, start, end);

    let db = Database::open(db_file).context("open 1History DB")?;

    let mut found = 0;
    let mut total_affected = 0;
    let mut total_duplicated = 0;
    let mut persist = |history_file: &str| {
        let s = Source::open(history_file).context("open")?;
        let rows = s.select(start, end).context("select")?.collect::<Vec<_>>();
        debug!("{:?} select {} histories", s.name(), rows.len());
        found += rows.len();

        info!("Begin backup {}...", &history_file);
        let collector = TUICollector::new(rows.len() as u64);
        if !dry_run {
            let (affected, duplicated) =
                db.persist(s.path(), rows, collector).context("persist")?;
            debug!(
                "{:?} affected:{}, duplicated:{}",
                s.name(),
                affected,
                duplicated
            );
            total_affected += affected;
            total_duplicated += duplicated;
        };
        info!("Finish backup {}", &history_file);

        Ok::<_, Error>(())
    };
    for his_file in &history_files {
        if let Err(e) = persist(his_file) {
            let msg = format!("{e:?}");
            if msg.contains("The database file is locked") {
                debug!("Open database directly failed, copy to temp and backup again");
                let mut tmp = match tempfile::NamedTempFile::new() {
                    Ok(f) => f,
                    Err(_) => continue,
                };
                let body = match fs::read(his_file) {
                    Ok(v) => v,
                    Err(_) => continue,
                };
                if let Err(e) = tmp.write_all(&body) {
                    debug!("Copy to backup file failed, msg:{e}");
                    continue;
                }
                let path = tmp.into_temp_path();
                let path = path.to_string_lossy();
                if let Err(e) = persist(&path) {
                    debug!("{} persist failed, backup:{}, err: {:?}", his_file, path, e);
                }
            }
        }
    }

    info!("Summary\nFound:{found}, Imported:{total_affected}, Duplicated: {total_duplicated}");
    Ok(())
}