diff options
author | fxqnlr <[email protected]> | 2023-05-21 13:43:52 +0200 |
---|---|---|
committer | fxqnlr <[email protected]> | 2023-05-21 13:43:52 +0200 |
commit | 016e1d8d760113a64afcc5d516f08010cb566d68 (patch) | |
tree | 3de1a3bfbd3c7266dab5288240720133ffd2f602 | |
parent | 5a2ea0755b29a8811aeeec1c73679c5783082628 (diff) | |
download | modlist-016e1d8d760113a64afcc5d516f08010cb566d68.tar modlist-016e1d8d760113a64afcc5d516f08010cb566d68.tar.gz modlist-016e1d8d760113a64afcc5d516f08010cb566d68.zip |
added multithreaded downloads and progressbar
-rw-r--r-- | Cargo.lock | 53 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/commands/modification.rs | 8 | ||||
-rw-r--r-- | src/files.rs | 160 |
4 files changed, 161 insertions, 61 deletions
@@ -232,6 +232,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
232 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" | 232 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" |
233 | 233 | ||
234 | [[package]] | 234 | [[package]] |
235 | name = "console" | ||
236 | version = "0.15.6" | ||
237 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
238 | checksum = "d0525278dce688103060006713371cedbad27186c7d913f33d866b498da0f595" | ||
239 | dependencies = [ | ||
240 | "encode_unicode", | ||
241 | "lazy_static", | ||
242 | "libc", | ||
243 | "unicode-width", | ||
244 | "windows-sys 0.45.0", | ||
245 | ] | ||
246 | |||
247 | [[package]] | ||
235 | name = "core-foundation" | 248 | name = "core-foundation" |
236 | version = "0.9.3" | 249 | version = "0.9.3" |
237 | source = "registry+https://github.com/rust-lang/crates.io-index" | 250 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -312,6 +325,12 @@ dependencies = [ | |||
312 | ] | 325 | ] |
313 | 326 | ||
314 | [[package]] | 327 | [[package]] |
328 | name = "encode_unicode" | ||
329 | version = "0.3.6" | ||
330 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
331 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" | ||
332 | |||
333 | [[package]] | ||
315 | name = "encoding_rs" | 334 | name = "encoding_rs" |
316 | version = "0.8.32" | 335 | version = "0.8.32" |
317 | source = "registry+https://github.com/rust-lang/crates.io-index" | 336 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -654,6 +673,18 @@ dependencies = [ | |||
654 | ] | 673 | ] |
655 | 674 | ||
656 | [[package]] | 675 | [[package]] |
676 | name = "indicatif" | ||
677 | version = "0.17.3" | ||
678 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
679 | checksum = "cef509aa9bc73864d6756f0d34d35504af3cf0844373afe9b8669a5b8005a729" | ||
680 | dependencies = [ | ||
681 | "console", | ||
682 | "number_prefix", | ||
683 | "portable-atomic 0.3.20", | ||
684 | "unicode-width", | ||
685 | ] | ||
686 | |||
687 | [[package]] | ||
657 | name = "instant" | 688 | name = "instant" |
658 | version = "0.1.12" | 689 | version = "0.1.12" |
659 | source = "registry+https://github.com/rust-lang/crates.io-index" | 690 | source = "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]] |
897 | name = "number_prefix" | ||
898 | version = "0.4.0" | ||
899 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
900 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" | ||
901 | |||
902 | [[package]] | ||
865 | name = "object" | 903 | name = "object" |
866 | version = "0.30.3" | 904 | version = "0.30.3" |
867 | source = "registry+https://github.com/rust-lang/crates.io-index" | 905 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -968,6 +1006,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||
968 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" | 1006 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" |
969 | 1007 | ||
970 | [[package]] | 1008 | [[package]] |
1009 | name = "portable-atomic" | ||
1010 | version = "0.3.20" | ||
1011 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1012 | checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" | ||
1013 | dependencies = [ | ||
1014 | "portable-atomic 1.3.2", | ||
1015 | ] | ||
1016 | |||
1017 | [[package]] | ||
1018 | name = "portable-atomic" | ||
1019 | version = "1.3.2" | ||
1020 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
1021 | checksum = "dc59d1bcc64fc5d021d67521f818db868368028108d37f0e98d74e33f68297b5" | ||
1022 | |||
1023 | [[package]] | ||
971 | name = "proc-macro2" | 1024 | name = "proc-macro2" |
972 | version = "1.0.56" | 1025 | version = "1.0.56" |
973 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -18,3 +18,4 @@ error-chain = "0.12.4" | |||
18 | dirs = "5.0.0" | 18 | dirs = "5.0.0" |
19 | clap = { version = "4.2.1", features = ["derive"] } | 19 | clap = { version = "4.2.1", features = ["derive"] } |
20 | clap_complete = "4.2.0" | 20 | clap_complete = "4.2.0" |
21 | indicatif = "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 @@ | |||
1 | use futures_util::StreamExt; | 1 | use futures_util::StreamExt; |
2 | use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; | ||
2 | use reqwest::Client; | 3 | use reqwest::Client; |
4 | use tokio::task::JoinSet; | ||
3 | use std::{ | 5 | use 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 | ||
9 | use crate::{ | 11 | use crate::{ |
@@ -15,83 +17,123 @@ use crate::{ | |||
15 | List, | 17 | List, |
16 | }; | 18 | }; |
17 | 19 | ||
18 | pub async fn download_versions(list: List, config: Cfg, versions: Vec<Version>) -> MLE<String> { | 20 | pub 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!(" ✓"); | 47 | async 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 | ||
84 | async fn download_file(url: String, path: String, name: String) -> MLE<()> { | 111 | async 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(()) |