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 {
path.replace('\\', "/")
.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 entry = reader.entries()[index];
let filename = entry.filename().to_string();
let path = out_dir.join(sanitize_file_path(filename.as_str()));
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));
if !path.exists() {
create_dir_all(&path)
.await
.expect("Failed to create extracted directory");
}
} else {
on_event(UnzipEvent::File(filename));
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>,
#[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);
}
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());
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?;
Ok(())
}