Add into-file cli argument

- Added option to set the filename for single file downloads
- Changed the number cli arguments to unsigned, since they can only be
  positive anyways
- Combined the cli arguments into a struct
This commit is contained in:
Daniel M 2021-04-02 15:58:16 +02:00
parent a933c57396
commit 2d2aaef9f8
3 changed files with 132 additions and 51 deletions

View File

@ -33,18 +33,18 @@ pub enum DlStatus {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DlReport { pub struct DlReport {
pub id: i32, pub id: u32,
pub status: DlStatus pub status: DlStatus
} }
#[derive(Clone)] #[derive(Clone)]
pub struct DlReporter { pub struct DlReporter {
id: i32, id: u32,
transmitter: mpsc::UnboundedSender<DlReport> transmitter: mpsc::UnboundedSender<DlReport>
} }
impl DlReporter { impl DlReporter {
pub fn new(id: i32, transmitter: mpsc::UnboundedSender<DlReport>) -> DlReporter { pub fn new(id: u32, transmitter: mpsc::UnboundedSender<DlReport>) -> DlReporter {
DlReporter { DlReporter {
id: id, id: id,
transmitter: transmitter transmitter: transmitter
@ -83,7 +83,7 @@ impl InfoHolder {
} }
fn print_accumulated_report(statuses: & HashMap<i32, InfoHolder>, msg_queue: &mut VecDeque<String>, moved_lines: u16) -> ResBE<u16> { fn print_accumulated_report(statuses: & HashMap<u32, InfoHolder>, msg_queue: &mut VecDeque<String>, moved_lines: u16) -> ResBE<u16> {
let mut dl_speed_sum = 0.0; let mut dl_speed_sum = 0.0;
execute!( execute!(
@ -142,7 +142,7 @@ fn print_accumulated_report(statuses: & HashMap<i32, InfoHolder>, msg_queue: &mu
pub async fn watch_and_print_reports(mut receiver: mpsc::UnboundedReceiver<DlReport>) -> ResBE<()> { pub async fn watch_and_print_reports(mut receiver: mpsc::UnboundedReceiver<DlReport>) -> ResBE<()> {
let mut statuses: HashMap<i32, InfoHolder> = HashMap::new(); let mut statuses: HashMap<u32, InfoHolder> = HashMap::new();
let mut moved_lines = 0; let mut moved_lines = 0;
let mut msg_queue = VecDeque::new(); let mut msg_queue = VecDeque::new();

View File

@ -229,7 +229,7 @@ pub async fn download_feedback_chunks(url: &str, into_file: &str, rep: DlReporte
// This will spin up multiple tasks that and manage the status updates for them. // This will spin up multiple tasks that and manage the status updates for them.
// The combined status will be reported back to the caller // The combined status will be reported back to the caller
pub async fn download_feedback_multi(url: &str, into_file: &str, rep: DlReporter, conn_count: i32, content_length: Option<u64>) -> ResBE<()> { pub async fn download_feedback_multi(url: &str, into_file: &str, rep: DlReporter, conn_count: u32, content_length: Option<u64>) -> ResBE<()> {
let content_length = match content_length { let content_length = match content_length {
Some(it) => it, Some(it) => it,

View File

@ -15,6 +15,26 @@ mod errors;
mod dlreport; mod dlreport;
#[derive(Clone, Debug)]
enum CLIAction {
DownloadUrl(String),
ResolveZippyUrl(String),
UrlList(String),
None
}
#[derive(Clone, Debug)]
struct CLIArguments {
outdir: String,
into_file: Option<String>,
file_count: u32,
conn_count: u32,
zippy: bool,
action: CLIAction,
urls: Vec<String>
}
#[tokio::main] #[tokio::main]
async fn main() -> ResBE<()> { async fn main() -> ResBE<()> {
@ -30,6 +50,15 @@ async fn main() -> ResBE<()> {
.help("Set the output directory. The directory will be created \ .help("Set the output directory. The directory will be created \
if it doesn't exit yet") if it doesn't exit yet")
) )
.arg(
Arg::with_name("into_file")
.short("i")
.long("into-file")
.value_name("FILENAME")
.takes_value(true)
.requires("download")
.help("Force filename. This only works for single file downloads")
)
.arg( .arg(
Arg::with_name("file_count") Arg::with_name("file_count")
.short("n") .short("n")
@ -45,9 +74,10 @@ async fn main() -> ResBE<()> {
.value_name("NUMBER OF CONNECTIONS") .value_name("NUMBER OF CONNECTIONS")
.takes_value(true) .takes_value(true)
.help("The number concurrent connections per file download. \ .help("The number concurrent connections per file download. \
Downloads might fail when the number of connections is too high. \ Downloads might fail when the number of connections is \
Files started with multiple connections can't be continued. \ too high. Files started with multiple connections can't \
NOTE: This will likely cause IO bottlenecks on HDDs") be continued. NOTE: This will likely cause IO \
bottlenecks on HDDs")
) )
.arg( .arg(
Arg::with_name("zippyshare") Arg::with_name("zippyshare")
@ -94,12 +124,17 @@ async fn main() -> ResBE<()> {
None => "./" None => "./"
}; };
let into_file = match arguments.value_of("into_file") {
Some(it) => Some(it.to_string()),
None => None
};
let file_count = match arguments.value_of("file_count") { let file_count = match arguments.value_of("file_count") {
Some(it) => it, Some(it) => it,
None => "1" None => "1"
}; };
let file_count: i32 = match file_count.parse() { let file_count: u32 = match file_count.parse() {
Ok(it) => it, Ok(it) => it,
Err(_) => { Err(_) => {
eprintln!("Invalid value for num-files: {}", file_count); eprintln!("Invalid value for num-files: {}", file_count);
@ -117,7 +152,7 @@ async fn main() -> ResBE<()> {
None => "1" None => "1"
}; };
let conn_count: i32 = match conn_count.parse() { let conn_count: u32 = match conn_count.parse() {
Ok(it) => it, Ok(it) => it,
Err(_) => { Err(_) => {
eprintln!("Invalid value for connections: {}", conn_count); eprintln!("Invalid value for connections: {}", conn_count);
@ -133,39 +168,64 @@ async fn main() -> ResBE<()> {
let is_zippy = arguments.is_present("zippyshare"); let is_zippy = arguments.is_present("zippyshare");
let action =
if let Some(listfile) = arguments.value_of("listfile") {
CLIAction::UrlList (
listfile.to_string()
)
} else if let Some(download_url) = arguments.value_of("download") {
CLIAction::DownloadUrl(
download_url.to_string()
)
} else if let Some(resolve_url) = arguments.value_of("zippy-resolve") {
CLIAction::ResolveZippyUrl(
resolve_url.to_string()
)
}
else {
CLIAction::None
};
let mut cli_args = CLIArguments {
outdir: outdir.to_string(),
into_file: into_file,
file_count: file_count,
conn_count: conn_count,
zippy: is_zippy,
action: action,
urls: Vec::new()
};
// Evaluate and execute the requested action. The 3 different actions are // Evaluate and execute the requested action. The 3 different actions are
// mutally exclusive, so only one of them will be executed // mutally exclusive, so only one of them will be executed
if let Some(s_listfile) = arguments.value_of("listfile") { match &cli_args.action {
let listfile = Path::new(s_listfile); CLIAction::UrlList(listfile) => {
if !listfile.is_file() { let p_listfile = Path::new(listfile);
eprintln!("Listfile '{}' does not exist!", s_listfile);
if !p_listfile.is_file() {
eprintln!("Listfile '{}' does not exist!", &listfile);
exit(1); exit(1);
} }
let ifile = std::fs::File::open(listfile)?; let ifile = std::fs::File::open(p_listfile)?;
let urls: Vec<String> = std::io::BufReader::new(ifile) cli_args.urls = std::io::BufReader::new(ifile)
.lines() .lines()
.map(|l| l.unwrap()) .map(|l| l.unwrap())
.filter(|url| url.len() > 0 && !url.starts_with("#")) .filter(|url| url.len() > 0 && !url.starts_with("#"))
.collect(); .collect();
},
download_multiple(urls, outdir, file_count, conn_count, is_zippy).await?; CLIAction::DownloadUrl(url) => {
cli_args.urls = vec![url.clone()];
} }
if let Some(url) = arguments.value_of("download") { CLIAction::ResolveZippyUrl(url) => {
match zippy::resolve_link(url).await {
download_multiple(vec![url.to_string()], outdir, 1, conn_count, is_zippy).await?;
}
if let Some(url) = arguments.value_of("zippy-resolve") {
match zippy::resolve_link(&url).await {
Ok(resolved_url) => { Ok(resolved_url) => {
println!("{}", resolved_url); println!("{}", resolved_url);
}, },
@ -173,16 +233,29 @@ async fn main() -> ResBE<()> {
println!("Zippyshare link could not be resolved"); println!("Zippyshare link could not be resolved");
exit(1); exit(1);
} }
}
},
CLIAction::None => {
eprintln!("No action selected. This should not happen");
exit(1);
} }
} }
Ok(()) download_multiple(cli_args).await
} }
async fn download_multiple(urls: Vec<String>, outdir: &str, file_count: i32, conn_count: i32, is_zippy: bool) -> ResBE<()> { async fn download_multiple(cli_args: CLIArguments) -> ResBE<()> {
let outdir = Path::new(outdir); let outdir = cli_args.outdir;
let outdir = Path::new(&outdir);
let file_count = cli_args.file_count;
let zippy = cli_args.zippy;
let conn_count = cli_args.conn_count;
if !outdir.exists() { if !outdir.exists() {
if let Err(_e) = std::fs::create_dir_all(outdir) { if let Err(_e) = std::fs::create_dir_all(outdir) {
@ -197,9 +270,9 @@ async fn download_multiple(urls: Vec<String>, outdir: &str, file_count: i32, con
let (tx, rx) = mpsc::unbounded_channel::<DlReport>(); let (tx, rx) = mpsc::unbounded_channel::<DlReport>();
for offset in 0..file_count { for offset in 0 .. file_count {
let urls: Vec<String> = urls let urls: Vec<String> = cli_args.urls
.iter() .iter()
.enumerate() .enumerate()
.filter(|(index, _)| (index) % file_count as usize == offset as usize) .filter(|(index, _)| (index) % file_count as usize == offset as usize)
@ -209,6 +282,8 @@ async fn download_multiple(urls: Vec<String>, outdir: &str, file_count: i32, con
let tx = tx.clone(); let tx = tx.clone();
let outdir = outdir.to_owned(); let outdir = outdir.to_owned();
let offset = offset; let offset = offset;
let arg_filename = cli_args.into_file.clone();
joiners.push(tokio::task::spawn(async move { joiners.push(tokio::task::spawn(async move {
for (i, url) in urls.iter().enumerate() { for (i, url) in urls.iter().enumerate() {
@ -216,11 +291,11 @@ async fn download_multiple(urls: Vec<String>, outdir: &str, file_count: i32, con
let tx = tx.clone(); let tx = tx.clone();
// Recalculated index in the main url vector, used as id // Recalculated index in the main url vector, used as id
let global_url_index = i as i32 * file_count + offset; let global_url_index = i as u32 * file_count + offset;
let rep = DlReporter::new(global_url_index, tx); let rep = DlReporter::new(global_url_index, tx);
let url = if is_zippy { let url = if zippy {
match zippy::resolve_link(&url).await { match zippy::resolve_link(&url).await {
Ok(url) => url, Ok(url) => url,
Err(_e) => { Err(_e) => {
@ -235,7 +310,13 @@ async fn download_multiple(urls: Vec<String>, outdir: &str, file_count: i32, con
url.to_string() url.to_string()
}; };
let file_name = download::url_to_filename(&url); let file_name = if let Some(arg_filename) = &arg_filename {
arg_filename.to_string()
} else {
download::url_to_filename(&url)
};
let into_file = outdir.join(Path::new(&file_name)) let into_file = outdir.join(Path::new(&file_name))
.to_str().unwrap().to_string(); .to_str().unwrap().to_string();
let path_into_file = Path::new(&into_file); let path_into_file = Path::new(&into_file);