use anyhow::{bail, Context, Result};
use clap::Clap;
use log::debug;
use std::path::{Path, PathBuf};
use std::string::String;
use crate::at::At;
use crate::cfg::Config;
use crate::cli::WaitingSpinner;
use crate::cmd::Command;
use crate::ssh::SshSession;
use crate::util::get_hash;
#[derive(Clap, Debug)]
pub struct Push {
#[clap(short, long)]
alias: Vec<String>,
#[clap(short, long)]
expire: Option<String>,
#[clap()]
files: Vec<PathBuf>,
#[clap(
short = 'l',
long,
conflicts_with = "limit-kbytes",
value_name = "Mbit/s"
)]
limit_mbits: Option<f64>,
#[clap(
short = 'L',
long,
conflicts_with = "limit-mbits",
value_name = "kByte/s"
)]
limit_kbytes: Option<f64>,
}
impl Push {
fn upload(
&self,
session: &SshSession,
config: &Config,
to_upload: &Path,
target_name: &str,
) -> Result<()> {
let mut target = PathBuf::new();
let prefix_length = session.host.prefix_length;
let hash = get_hash(to_upload, prefix_length)
.with_context(|| format!("Could not read {} to compute hash.", to_upload.display()))?;
let mut expire_addendum = String::new();
let expirer = if let Some(delay) = self.expire.as_ref() {
expire_addendum = format!(" (expiring after {})", delay);
Some(At::new(session, &delay)?)
} else {
None
};
target.push(&hash);
let folder = target.clone();
session.make_folder(&folder)?;
target.push(target_name);
session.upload_file(
&to_upload,
&target,
self.limit_mbits
.map(|f| {
(f * 1024.0 * 1024.0 / 8.0) as usize
})
.or(self.limit_kbytes.map(|f| {
(f * 1024.0) as usize
})),
)?;
if config.verify_via_hash {
debug!("Verifying upload..");
let spinner = WaitingSpinner::new("Verifying upload..".to_string());
let remote_hash = session.get_remote_hash(&target, prefix_length)?;
if hash != remote_hash {
session.remove_folder(&folder)?;
bail!(
"[{}] Hashes differ: local={} remote={}",
to_upload.display(),
hash,
remote_hash
);
}
spinner.finish();
debug!("Done");
}
if let Some(group) = &session.host.group {
session.adjust_group(&folder, &group)?;
};
if let Some(expirer) = expirer {
expirer.expire(&target)?;
}
print!(
"{}{}",
session
.host
.get_url(&format!("{}/{}", &hash, &target_name))?,
expire_addendum
);
println!();
Ok(())
}
}
impl Command for Push {
fn run(&self, session: &SshSession, config: &Config) -> Result<()> {
let mut aliases: Vec<String> = vec![];
if self.files.len() == 0 && self.alias.len() == 0 {
bail!("No files to upload specified.");
} else if self.files.len() == 0 && self.alias.len() > 0 {
bail!(
"No files to upload specified. \
Did you forget to seperate --alias option via double dashes from files to upload?"
);
} else if self.alias.len() > 0 && self.alias.len() != self.files.len() {
bail!("You need to specify as many aliases as you specify files!");
} else if self.alias.len() == 0 {
for file in self.files.iter() {
aliases.push(
file.file_name()
.with_context(|| format!("{} has no filename.", file.display()))?
.to_str()
.with_context(|| format!("{} has invalid filename", file.display()))?
.to_string(),
);
}
} else {
aliases = self.alias.clone();
}
if let Some(limit) = self.limit_mbits {
debug!("Limiting upload to {} Mbit/s", limit);
}
if let Some(limit) = self.limit_kbytes {
debug!("Limiting upload to {} kByte/s", limit);
}
for (to_upload, alias) in self.files.iter().zip(aliases.iter()) {
self.upload(session, config, to_upload, alias)?;
}
Ok(())
}
}