summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfxqnlr <[email protected]>2023-05-21 13:43:52 +0200
committerfxqnlr <[email protected]>2023-05-21 13:43:52 +0200
commit016e1d8d760113a64afcc5d516f08010cb566d68 (patch)
tree3de1a3bfbd3c7266dab5288240720133ffd2f602
parent5a2ea0755b29a8811aeeec1c73679c5783082628 (diff)
downloadmodlist-016e1d8d760113a64afcc5d516f08010cb566d68.tar
modlist-016e1d8d760113a64afcc5d516f08010cb566d68.tar.gz
modlist-016e1d8d760113a64afcc5d516f08010cb566d68.zip
added multithreaded downloads and progressbar
-rw-r--r--Cargo.lock53
-rw-r--r--Cargo.toml1
-rw-r--r--src/commands/modification.rs8
-rw-r--r--src/files.rs160
4 files changed, 161 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c842b90..394a3e6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -232,6 +232,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
232checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 232checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
233 233
234[[package]] 234[[package]]
235name = "console"
236version = "0.15.6"
237source = "registry+https://github.com/rust-lang/crates.io-index"
238checksum = "d0525278dce688103060006713371cedbad27186c7d913f33d866b498da0f595"
239dependencies = [
240 "encode_unicode",
241 "lazy_static",
242 "libc",
243 "unicode-width",
244 "windows-sys 0.45.0",
245]
246
247[[package]]
235name = "core-foundation" 248name = "core-foundation"
236version = "0.9.3" 249version = "0.9.3"
237source = "registry+https://github.com/rust-lang/crates.io-index" 250source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -312,6 +325,12 @@ dependencies = [
312] 325]
313 326
314[[package]] 327[[package]]
328name = "encode_unicode"
329version = "0.3.6"
330source = "registry+https://github.com/rust-lang/crates.io-index"
331checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
332
333[[package]]
315name = "encoding_rs" 334name = "encoding_rs"
316version = "0.8.32" 335version = "0.8.32"
317source = "registry+https://github.com/rust-lang/crates.io-index" 336source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -654,6 +673,18 @@ dependencies = [
654] 673]
655 674
656[[package]] 675[[package]]
676name = "indicatif"
677version = "0.17.3"
678source = "registry+https://github.com/rust-lang/crates.io-index"
679checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729"
680dependencies = [
681 "console",
682 "number_prefix",
683 "portable-atomic 0.3.20",
684 "unicode-width",
685]
686
687[[package]]
657name = "instant" 688name = "instant"
658version = "0.1.12" 689version = "0.1.12"
659source = "registry+https://github.com/rust-lang/crates.io-index" 690source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -806,6 +837,7 @@ dependencies = [
806 "dirs", 837 "dirs",
807 "error-chain", 838 "error-chain",
808 "futures-util", 839 "futures-util",
840 "indicatif",
809 "reqwest", 841 "reqwest",
810 "rusqlite", 842 "rusqlite",
811 "serde", 843 "serde",
@@ -862,6 +894,12 @@ dependencies = [
862] 894]
863 895
864[[package]] 896[[package]]
897name = "number_prefix"
898version = "0.4.0"
899source = "registry+https://github.com/rust-lang/crates.io-index"
900checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
901
902[[package]]
865name = "object" 903name = "object"
866version = "0.30.3" 904version = "0.30.3"
867source = "registry+https://github.com/rust-lang/crates.io-index" 905source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -968,6 +1006,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
968checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 1006checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
969 1007
970[[package]] 1008[[package]]
1009name = "portable-atomic"
1010version = "0.3.20"
1011source = "registry+https://github.com/rust-lang/crates.io-index"
1012checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e"
1013dependencies = [
1014 "portable-atomic 1.3.2",
1015]
1016
1017[[package]]
1018name = "portable-atomic"
1019version = "1.3.2"
1020source = "registry+https://github.com/rust-lang/crates.io-index"
1021checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5"
1022
1023[[package]]
971name = "proc-macro2" 1024name = "proc-macro2"
972version = "1.0.56" 1025version = "1.0.56"
973source = "registry+https://github.com/rust-lang/crates.io-index" 1026source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 345f60d..a4b51bc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,3 +18,4 @@ error-chain = "0.12.4"
18dirs = "5.0.0" 18dirs = "5.0.0"
19clap = { version = "4.2.1", features = ["derive"] } 19clap = { version = "4.2.1", features = ["derive"] }
20clap_complete = "4.2.0" 20clap_complete = "4.2.0"
21indicatif = "0.17.3"
diff --git a/src/commands/modification.rs b/src/commands/modification.rs
index 9a1a651..d4c49d6 100644
--- a/src/commands/modification.rs
+++ b/src/commands/modification.rs
@@ -140,6 +140,8 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
140 140
141 let mut ids = vec![]; 141 let mut ids = vec![];
142 142
143 println!("{:?}", mod_ids);
144
143 for id in mod_ids { 145 for id in mod_ids {
144 setmap.insert(id.0.to_string(), id.1); 146 setmap.insert(id.0.to_string(), id.1);
145 ids.push(id.0); 147 ids.push(id.0);
@@ -195,14 +197,16 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
195 available_versions_vec.push(ver.id); 197 available_versions_vec.push(ver.id);
196 } 198 }
197 199
200 println!("{:?}", setmap);
201
198 projectinfo.push(ProjectInfo { 202 projectinfo.push(ProjectInfo {
199 mod_id: String::from(&project.id), 203 mod_id: String::from(&project.id),
200 slug: project.slug, 204 slug: project.slug.clone(),
201 title: project.title, 205 title: project.title,
202 current_version, 206 current_version,
203 applicable_versions: available_versions_vec, 207 applicable_versions: available_versions_vec,
204 download_link: file, 208 download_link: file,
205 set_version: setmap.get(&project.id).unwrap().clone(), 209 set_version: setmap.get(&project.slug).unwrap().clone(),
206 }) 210 })
207 } else { 211 } else {
208 println!("\t └There's currently no mod version for your specified target"); 212 println!("\t └There's currently no mod version for your specified target");
diff --git a/src/files.rs b/src/files.rs
index a4c128e..a4a1d3b 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,9 +1,11 @@
1use futures_util::StreamExt; 1use futures_util::StreamExt;
2use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
2use reqwest::Client; 3use reqwest::Client;
4use tokio::task::JoinSet;
3use std::{ 5use std::{
4 collections::HashMap, 6 collections::HashMap,
5 fs::{copy, read_dir, remove_file, rename, File}, 7 fs::{copy, read_dir, remove_file, rename, File},
6 io::Write, 8 io::Write, cmp::min,
7}; 9};
8 10
9use crate::{ 11use crate::{
@@ -15,83 +17,123 @@ use crate::{
15 List, 17 List,
16}; 18};
17 19
18pub async fn download_versions(list: List, config: Cfg, versions: Vec<Version>) -> MLE<String> { 20pub async fn download_versions(list: List, config: Cfg, versions: Vec<Version>) -> MLE<()> {
19 let mut cached = get_cached_versions(&config.cache); 21 let cached = get_cached_versions(&config.cache);
20 22
21 // println!("{:#?}", cached); 23 // println!("{:#?}", cached);
22 24
23 let dl_path = String::from(&list.download_folder); 25 // println!(" └Download mods to {}", dl_path);
26
27 let mp = MultiProgress::new();
24 28
25 println!(" └Download mods to {}", dl_path); 29 let mut js = JoinSet::new();
30 let style = ProgressStyle::with_template("{spinner:.green}{msg}\t[{bar:.green/lime}] {bytes}/{total_bytes}")
31 .unwrap()
32 .progress_chars("#>-");
26 33
27 for ver in versions { 34 for ver in versions {
28 let project_info = mods_get_info(config.clone(), &ver.project_id)?; 35 let p = mp.add(ProgressBar::new(1));
29 36 p.set_style(style.clone());
30 //Check cache if already downloaded 37 js.spawn(download_version(config.clone(), list.clone(), ver, cached.clone(), p));
31 let c = cached.remove(&ver.id); 38 }
32 if c.is_some() { 39
33 print!( 40 mp.clear().unwrap();
34 "\t└({})Get version {} from cache", 41
35 project_info.title, ver.id 42 while js.join_next().await.is_some() {}
36 ); 43
37 //Force flush of stdout, else print! doesn't print instantly 44 Ok(())
38 std::io::stdout().flush()?; 45}
39 copy_cached_version(&c.unwrap(), &dl_path); 46
40 println!(" ✓"); 47async fn download_version(config: Cfg, list: List, version: Version, mut cached: HashMap<String, String>, progress: ProgressBar) -> MLE<()> {
41 } else { 48 let project_info = mods_get_info(config.clone(), &version.project_id)?;
42 print!("\t└({})Download version {}", project_info.title, ver.id); 49
43 //Force flush of stdout, else print! doesn't print instantly 50 let dl_path = String::from(&list.download_folder);
44 std::io::stdout().flush().unwrap(); 51
45 let files = ver.files; 52 progress.set_message(String::from(&version.id));
46 let file = match files.clone().into_iter().find(|f| f.primary) { 53
47 Some(f) => f, 54 //Check cache if already downloaded
48 None => files[0].clone() 55 let c = cached.remove(&version.id);
49 }; 56 if c.is_some() {
50 let mut splitname: Vec<&str> = file.filename.split('.').collect(); 57 print!(
51 let extension = match splitname.pop().ok_or("") { 58 "\t└({})Get version {} from cache",
52 Ok(e) => e, 59 project_info.title, version.id
53 Err(..) => return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION")), 60 );
54 }; 61 //Force flush of stdout, else print! doesn't print instantly
55 let filename = format!( 62 std::io::stdout().flush()?;
56 "{}.mr.{}.{}.{}", 63 copy_cached_version(&c.unwrap(), &dl_path);
57 splitname.join("."), 64 println!(" ✓");
58 ver.project_id, 65 } else {
59 ver.id, 66 // print!("\t└({})Download version {}", project_info.title, version.id);
60 extension 67 //Force flush of stdout, else print! doesn't print instantly
61 ); 68 std::io::stdout().flush().unwrap();
62 download_file( 69 let files = version.files;
63 file.url, 70 let file = match files.clone().into_iter().find(|f| f.primary) {
64 list.clone().download_folder, 71 Some(f) => f,
65 filename.clone(), 72 None => files[0].clone()
66 ) 73 };
67 .await?; 74 let mut splitname: Vec<&str> = file.filename.split('.').collect();
68 println!(" ✓"); 75 let extension = match splitname.pop().ok_or("") {
69 //Copy file to cache 76 Ok(e) => e,
70 print!("\t └Copy to cache"); 77 Err(..) => return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION")),
71 //Force flush of stdout, else print! doesn't print instantly 78 };
72 std::io::stdout().flush().unwrap(); 79 let filename = format!(
73 let dl_path_file = format!("{}/{}", list.download_folder, filename); 80 "{}.mr.{}.{}.{}",
74 let cache_path = format!("{}/{}", &config.clone().cache, filename); 81 splitname.join("."),
75 // println!("{}:{}", dl_path_file, cache_path); 82 version.project_id,
76 copy(dl_path_file, cache_path)?; 83 version.id,
77 println!(" ✓"); 84 extension
78 } 85 );
86
87 download_file(
88 &file.url,
89 &list.download_folder,
90 &filename,
91 &progress
92 )
93 .await?;
94 // println!(" ✓");
95 //Copy file to cache
96 // print!("\t └Copy to cache");
97 //Force flush of stdout, else print! doesn't print instantly
98 std::io::stdout().flush().unwrap();
99 let dl_path_file = format!("{}/{}", list.download_folder, filename);
100 let cache_path = format!("{}/{}", &config.clone().cache, filename);
101 // println!("{}:{}", dl_path_file, cache_path);
102 copy(dl_path_file, cache_path)?;
103 // println!(" ✓");
79 } 104 }
80 105
81 Ok(dl_path) 106 progress.finish_with_message(format!("✓{}", version.id));
107
108 Ok(())
82} 109}
83 110
84async fn download_file(url: String, path: String, name: String) -> MLE<()> { 111async fn download_file(url: &str, path: &str, name: &str, progress: &ProgressBar) -> MLE<()> {
85 let dl_path_file = format!("{}/{}", path, name); 112 let dl_path_file = format!("{}/{}", path, name);
86 let res = Client::new().get(String::from(&url)).send().await?; 113 let res = Client::new().get(url).send().await?;
114
115 let size = res.content_length().expect("Couldn't get content length");
116
117 progress.set_length(size);
118 // progress.set_style(ProgressStyle::with_template("{spinner:.green}{msg}\t[{wide_bar:.green/lime}] {bytes}/{total_bytes}").unwrap().progress_chars("#>-"));
87 119
88 // download chunks 120 // download chunks
89 let mut file = File::create(&dl_path_file)?; 121 let mut file = File::create(&dl_path_file)?;
90 let mut stream = res.bytes_stream(); 122 let mut stream = res.bytes_stream();
91 123
124 let mut downloaded: u64 = 0;
125
92 while let Some(item) = stream.next().await { 126 while let Some(item) = stream.next().await {
127 progress.inc(1);
93 let chunk = item?; 128 let chunk = item?;
94 file.write_all(&chunk)?; 129 file.write_all(&chunk)?;
130
131 // Progress bar
132 let new = min(downloaded + (chunk.len() as u64), size);
133 downloaded = new;
134 progress.set_position(new);
135
136 // std::thread::sleep(std::time::Duration::from_millis(100));
95 } 137 }
96 138
97 Ok(()) 139 Ok(())