dwh 0.7.1

A library to use the digitronic protocol dwh
Documentation
use std::sync::Arc;

use clap::Parser;
use dwh::{
    args::{ConnectionArgs, Timeout},
    update::UpdateOptions,
    BackdoorReauthHandler,
};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use tempdir::TempDir;

use std::path::{Path, PathBuf};

use async_zip::read::seek::ZipFileReader;
use tokio::fs::{create_dir_all, File, OpenOptions};

fn sanitize_file_path(path: &str) -> PathBuf {
    // Replaces backwards slashes
    path.replace('\\', "/")
        // Sanitizes each component
        .split('/')
        .map(sanitize_filename::sanitize)
        .collect()
}

pub enum UnzipEvent {
    Dir(String),
    File(String),
}

async fn unzip_file<F: Fn(UnzipEvent)>(
    archive: File,
    out_dir: &Path,
    on_event: F,
) -> dwh::Result<()> {
    let mut reader = ZipFileReader::new(archive).await?;
    for index in 0..reader.entries().len() {
        //let entr

        let entry = reader.entries()[index];
        let filename = entry.filename().to_string();
        let path = out_dir.join(sanitize_file_path(filename.as_str()));
        // If the filename of the entry ends with '/', it is treated as a directory.
        // This is implemented by previous versions of this crate and the Python Standard Library.
        // https://docs.rs/async_zip/0.0.8/src/async_zip/read/mod.rs.html#63-65
        // https://github.com/python/cpython/blob/820ef62833bd2d84a141adedd9a05998595d6b6d/Lib/zipfile.py#L528
        let entry_is_dir = entry.filename().ends_with('/');
        let mut entry_reader = reader.entry_reader(index).await?;
        if entry_is_dir {
            on_event(UnzipEvent::Dir(filename));
            // The directory may have been created if iteration is out of order.
            if !path.exists() {
                create_dir_all(&path)
                    .await
                    .expect("Failed to create extracted directory");
            }
        } else {
            on_event(UnzipEvent::File(filename));
            // Creates parent directories. They may not exist if iteration is out of order
            // or the archive does not contain directory entries.
            let parent = path
                .parent()
                .ok_or(dwh::error::Error::new("Failed to resolve parent directory"))?;
            if !parent.is_dir() {
                create_dir_all(parent).await?
            }
            let mut writer = OpenOptions::new()
                .write(true)
                .create_new(true)
                .open(&path)
                .await?;
            tokio::io::copy(&mut entry_reader, &mut writer).await?;
        }
    }
    Ok(())
}

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]

struct Args {
    source: String,
    host: String,

    #[arg(short, long)]
    user: Option<String>,
    #[arg(short, long)]
    password: Option<String>,
    // user will be magically logged in
    #[arg(short, long)]
    auto_login: bool,

    #[arg(short, long)]
    include: Vec<String>,
    #[arg(short, long)]
    exclude: Vec<String>,

    #[command(flatten)]
    pub connection_args: ConnectionArgs,
    #[command(flatten)]
    pub timeout: Timeout,
}

pub async fn unzip_to_temp<F: Fn(UnzipEvent)>(
    zip_file: String,
    on_event: F,
) -> dwh::Result<TempDir> {
    let temp_dir = TempDir::new("dw_update")?;
    let file = tokio::fs::File::open(zip_file.as_str()).await?;
    unzip_file(file, temp_dir.path(), on_event).await?;
    Ok(temp_dir)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    tracing::info!("{:?}", args);
    let mut dwh = dwh::new(args.host)?;
    dwh.re_auth_handler = Box::new(BackdoorReauthHandler {});
    dwh.set_hooks(args.connection_args.into_hooks().await?);
    args.timeout.apply(&mut dwh);
    if args.auto_login {
        dwh.use_backdooor_and_login().await?;
    }
    let mut source = args.source.clone();
    let is_zip = source.ends_with(".zip");
    let mut _temp_dir: Option<TempDir>;
    if is_zip {
        let unzipped_dir = unzip_to_temp("./demo-data.zip".to_string(), |event| match event {
            UnzipEvent::Dir(path) => tracing::info!("unzip: {}", path),
            UnzipEvent::File(path) => tracing::info!("unzip: {}", path),
        })
        .await?;
        source = unzipped_dir.path().to_str().unwrap().to_owned();
        _temp_dir = Some(unzipped_dir);
    }

    //
    // .await?;
    // println!("{}", temp_dir.path().display());
    // println!("wait motherfucker");

    // let mut dwh = dwh::new("http://192.168.178.55".to_owned())?;
    // dwh.use_backdooor_and_login().await?;
    let m = MultiProgress::new();
    let sty = ProgressStyle::with_template(
        "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes}",
    )
    .unwrap()
    .progress_chars("#>-");

    let pb_file = m.add(ProgressBar::new(0));
    pb_file.set_style(sty.clone());
    let pb_total = Arc::new(m.insert_after(&pb_file, ProgressBar::new(0)));
    pb_total.set_style(sty.clone());
    //zip_extract::extract(zip_file, "./out").await?;
    //m.clear()
    dwh::update::Update::new(
        dwh,
        move |event| match event {
            dwh::update::UpdateEvent::UpdateProgress(progress) => {
                if pb_total.length().unwrap() == 0 {
                    pb_total.set_length(progress.total as u64);
                }
                pb_total.set_position(progress.sent as u64);
            }
            dwh::update::UpdateEvent::File(path) => {
                pb_file.set_position(0);
                pb_file.set_length(0);
                m.println(format!("uploading file: {path}")).ok();
            }
            dwh::update::UpdateEvent::FileProgress(progress) => {
                if pb_file.length().unwrap() != progress.total as u64 {
                    pb_file.set_length(progress.total as u64);
                }
                pb_file.set_position(progress.sent as u64);
            }
            dwh::update::UpdateEvent::Dir(path) => {
                m.println(format!("Creating directory: {path}")).ok();
            }
            dwh::update::UpdateEvent::Err(err) => {
                m.println(format!("Error occurred: {err}")).ok();
            }
            dwh::update::UpdateEvent::Done => {
                m.println("files uploaded successful").ok();
            }
            dwh::update::UpdateEvent::Restart(duration) => {
                let secs = duration.as_secs();
                m.remove(&pb_file);
                m.remove(&pb_total);
                m.println(format!("Performing restart with timeout {secs}s"))
                    .ok();
            }
        },
        None,
        Some(UpdateOptions {
            include: args.include,
            exclude: args.exclude,
        }),
    )?
    .run(source)
    .await?;
    //m.clear();
    Ok(())
}