1use crate::Verbosity;
2use clap::crate_version;
3use colored::Colorize;
4use http::{Response, StatusCode};
5use std::fs::File;
6use std::path::Path;
7use std::time::Duration;
8use std::{io, process};
9use ureq::config::Config;
10use ureq::{Agent, Body};
11use url::Url;
12
13pub fn get_and_save(
14 url: &str,
15 script: &Option<(String, String)>,
16 output: &Option<String>,
17 timeout_connect_secs: u16,
18 max_time: u16,
19 headers: &[(String, String)],
20 verbosity: Verbosity,
21) {
22 let mut url = url.to_string();
23 let parsed_url = match Url::parse(&url) {
24 Ok(r) => r,
25 Err(e) => {
26 eprintln!("{}: invalid URL - {}", "ERROR".red(), e);
27 process::exit(3);
28 }
29 };
30 let path = if parsed_url.path() == "/" {
31 output.as_ref().unwrap_or_else(|| {
32 eprintln!(
33 "{}: URL without filename, you have to provide \
34 the filename where to store the file with the argument {}",
35 "ERROR".red(),
36 "-o, --output".yellow()
37 );
38 process::exit(4);
39 })
40 } else {
41 parsed_url.path()
42 };
43 if let Some(script) = script
44 && !url.contains(&script.0)
45 {
46 eprintln!(
47 "{}: the left part of the script '{}' is not part of the URL",
48 "ERROR".red(),
49 script.0.yellow()
50 );
51 process::exit(10);
52 }
53 let path = Path::new(path);
54 let config: Config = Agent::config_builder()
55 .timeout_connect(Option::from(Duration::from_secs(
56 timeout_connect_secs.into(),
57 )))
58 .timeout_global(Option::from(Duration::from_secs(max_time.into())))
59 .user_agent(format!("pose/{}", crate_version!()).as_str())
60 .http_status_as_error(false)
61 .build();
62 let mut result = _get_and_save(
63 &url,
64 output,
65 path,
66 config.clone(),
67 headers,
68 verbosity.clone(),
69 );
70 if !result && let Some(script) = script {
71 url = url.replace(&script.0, &script.1);
72 result = _get_and_save(&url, output, path, config, headers, verbosity.clone());
73 }
74 if !result {
75 eprintln!("{}: Download failed", "ERROR".red());
76 process::exit(1);
77 }
78}
79
80fn _get_and_save(
81 url: &str,
82 output: &Option<String>,
83 path: &Path,
84 config: Config,
85 headers: &[(String, String)],
86 verbosity: Verbosity,
87) -> bool {
88 if !matches!(verbosity, Verbosity::Quiet) {
89 eprint!("{}: Downloading {} ... ", "DEBUG".green(), url);
90 }
91 let agent: Agent = config.into();
92 let mut request = agent.get(url);
93 for header in headers {
94 request = request.header(&header.0, &header.1);
95 }
96 match request.call() {
97 Ok(mut resp) => {
98 if resp.status().is_success() {
99 if !matches!(verbosity, Verbosity::Quiet) {
100 eprintln!("{}", "found".green());
101 }
102 save(resp, path, output, verbosity.clone());
103 true
104 } else if resp.status() == StatusCode::NOT_FOUND {
105 if !matches!(verbosity, Verbosity::Quiet) {
106 eprintln!("{}", "not found".purple());
107 }
108 false
109 } else {
110 if !matches!(verbosity, Verbosity::Quiet) {
111 eprintln!("{}", "failed".red())
112 }
113 eprintln!(
114 "{}: {:?} {} {}",
115 "ERROR".red(),
116 resp.version(),
117 resp.status().as_u16(),
118 resp.status().canonical_reason().unwrap_or("")
119 );
120 let error_msg = resp.body_mut().read_to_string().unwrap_or_else(|e| {
121 eprintln!("{}: reading download content - {}", "ERROR".red(), e);
122 process::exit(5);
123 });
124 eprintln!("{}", error_msg);
125 process::exit(5);
126 }
127 }
128 Err(e) => {
129 if !matches!(verbosity, Verbosity::Quiet) {
130 eprintln!("{}", "failed".red())
131 }
132 eprintln!("{}: {}", "ERROR".red(), e);
133 process::exit(7);
134 }
135 }
136}
137
138fn save(resp: Response<Body>, path: &Path, output: &Option<String>, verbosity: Verbosity) {
139 let filename = if let Some(filename) = output {
140 if !matches!(verbosity, Verbosity::Quiet) {
141 eprint!(
142 "{}: Saving downloaded file as {} ... ",
143 "DEBUG".green(),
144 filename.yellow()
145 );
146 }
147 filename
148 } else {
149 path.file_name().unwrap().to_str().unwrap()
150 };
151 let mut content = resp.into_body().into_reader();
152 let mut file = File::create(filename).unwrap_or_else(|e| {
153 if !matches!(verbosity, Verbosity::Quiet) {
154 eprintln!("{}", "failed".red())
155 }
156 eprintln!(
157 "{}: creating file '{}' - {}",
158 "ERROR".red(),
159 filename.yellow(),
160 e
161 );
162 process::exit(5);
163 });
164 io::copy(&mut content, &mut file).unwrap_or_else(|e| {
165 if !matches!(verbosity, Verbosity::Quiet) {
166 eprintln!("{}", "failed".red());
167 }
168 eprintln!(
169 "{}: writing output to file '{}': {}",
170 "ERROR".red(),
171 filename.yellow(),
172 e
173 );
174 process::exit(6);
175 });
176 if !matches!(verbosity, Verbosity::Quiet) && output.is_some() {
177 eprintln!("{}", "done".green());
178 }
179}