summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorfx <[email protected]>2023-05-29 18:02:08 +0200
committerfx <[email protected]>2023-05-29 18:02:08 +0200
commitd3870a2efa74e68c643dfb4aef32edc2536503b0 (patch)
tree116075aaa57c35afca2749719d450c3cb473ab3e /src
parent5a2ea0755b29a8811aeeec1c73679c5783082628 (diff)
parentc7ecf3019a75dc0ab1a0aefeb9b880899fc8a231 (diff)
downloadmodlist-d3870a2efa74e68c643dfb4aef32edc2536503b0.tar
modlist-d3870a2efa74e68c643dfb4aef32edc2536503b0.tar.gz
modlist-d3870a2efa74e68c643dfb4aef32edc2536503b0.zip
Merge pull request 'multithreaded' (#6) from multithreaded into master
Reviewed-on: http://raspberrypi.fritz.box:7920/fx/modlist/pulls/6
Diffstat (limited to 'src')
-rw-r--r--src/apis/modrinth.rs19
-rw-r--r--src/cache.rs4
-rw-r--r--src/commands/download.rs93
-rw-r--r--src/commands/io.rs72
-rw-r--r--src/commands/list.rs85
-rw-r--r--src/commands/modification.rs204
-rw-r--r--src/commands/update.rs222
-rw-r--r--src/config.rs51
-rw-r--r--src/db.rs216
-rw-r--r--src/error.rs14
-rw-r--r--src/files.rs199
-rw-r--r--src/lib.rs72
-rw-r--r--src/main.rs206
13 files changed, 905 insertions, 552 deletions
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs
index 525cc0d..9a22633 100644
--- a/src/apis/modrinth.rs
+++ b/src/apis/modrinth.rs
@@ -127,10 +127,13 @@ pub enum GameVersionType {
127 release, 127 release,
128 snapshot, 128 snapshot,
129 alpha, 129 alpha,
130 beta 130 beta,
131} 131}
132 132
133async fn get(api: &str, path: &str) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> { 133async fn get(
134 api: &str,
135 path: &str,
136) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
134 let url = format!(r#"{}{}"#, api, path); 137 let url = format!(r#"{}{}"#, api, path);
135 138
136 let client = Client::builder() 139 let client = Client::builder()
@@ -170,7 +173,7 @@ pub async fn projects(api: &str, ids: Vec<String>) -> Vec<Project> {
170pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> { 173pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
171 let url = format!( 174 let url = format!(
172 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, 175 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#,
173 id, list.modloader.to_string(), list.mc_version 176 id, list.modloader, list.mc_version
174 ); 177 );
175 178
176 let data = get(api, &url).await.unwrap(); 179 let data = get(api, &url).await.unwrap();
@@ -182,7 +185,10 @@ pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
182} 185}
183 186
184///Get version with the version ids 187///Get version with the version ids
185pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { 188pub async fn get_raw_versions(
189 api: &str,
190 versions: Vec<String>,
191) -> Vec<Version> {
186 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); 192 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
187 193
188 let data = get(api, &url).await.unwrap().unwrap(); 194 let data = get(api, &url).await.unwrap().unwrap();
@@ -208,7 +214,10 @@ pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> {
208} 214}
209 215
210pub async fn get_game_versions() -> Vec<GameVersion> { 216pub async fn get_game_versions() -> Vec<GameVersion> {
211 let data = get("https://api.modrinth.com/v2/", "tag/game_version").await.unwrap().unwrap(); 217 let data = get("https://api.modrinth.com/v2/", "tag/game_version")
218 .await
219 .unwrap()
220 .unwrap();
212 221
213 serde_json::from_slice(&data).unwrap() 222 serde_json::from_slice(&data).unwrap()
214} 223}
diff --git a/src/cache.rs b/src/cache.rs
index c928670..8df4d2f 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -31,7 +31,7 @@ pub fn get_cached_versions(path: &str) -> HashMap<String, String> {
31/// Panics if . 31/// Panics if .
32pub fn copy_cached_version(version_path: &str, download_path: &str) { 32pub fn copy_cached_version(version_path: &str, download_path: &str) {
33 let versplit: Vec<&str> = version_path.split('/').collect(); 33 let versplit: Vec<&str> = version_path.split('/').collect();
34 let download = format!("{}/{}", download_path, versplit[versplit.len() - 1]); 34 let download =
35 // println!("{:#?}", download); 35 format!("{}/{}", download_path, versplit[versplit.len() - 1]);
36 copy(version_path, download).unwrap(); 36 copy(version_path, download).unwrap();
37} 37}
diff --git a/src/commands/download.rs b/src/commands/download.rs
index ebfb4eb..a7cf744 100644
--- a/src/commands/download.rs
+++ b/src/commands/download.rs
@@ -1,26 +1,48 @@
1use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2
1use crate::{config::Cfg, List}; 3use crate::{config::Cfg, List};
2use crate::{ 4use crate::{
3 db::userlist_get_all_current_versions_with_mods, 5 db::userlist_get_all_current_versions_with_mods,
4 error::{ErrorType, MLError, MLE}, 6 error::{ErrorType, MLError, MLE},
5 files::{ 7 files::{
6 clean_list_dir, delete_version, disable_version, download_versions, get_downloaded_versions, 8 clean_list_dir, delete_version, disable_version, download_versions,
9 get_downloaded_versions,
7 }, 10 },
8 modrinth::get_raw_versions, 11 modrinth::get_raw_versions,
9}; 12};
13use crate::{PROGRESS_CHARS, STYLE_BAR_POS};
10 14
11pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old: bool) -> MLE<()> { 15pub async fn download(
16 config: &Cfg,
17 liststack: Vec<List>,
18 clean: bool,
19 delete_old: bool,
20) -> MLE<()> {
21 let mp = MultiProgress::new();
22 let download_p =
23 mp.add(ProgressBar::new(liststack.len().try_into().unwrap()));
24 download_p.set_style(
25 ProgressStyle::with_template(STYLE_BAR_POS)
26 .unwrap()
27 .progress_chars(PROGRESS_CHARS),
28 );
12 29
13 for current_list in liststack { 30 for current_list in liststack {
14 println!("Downloading current versions of mods in {}", current_list.id); 31 download_p.set_message(format!("Download in {}", current_list.id));
15 let downloaded_versions = get_downloaded_versions(current_list.clone())?; 32
16 // println!("To download: {:#?}", downloaded_versions); 33 let downloaded_versions =
17 let current_version_ids = match userlist_get_all_current_versions_with_mods( 34 get_downloaded_versions(current_list.clone())?;
18 config.clone(), 35 let current_version_ids =
19 String::from(&current_list.id), 36 match userlist_get_all_current_versions_with_mods(
20 ) { 37 config,
21 Ok(i) => Ok(i), 38 String::from(&current_list.id),
22 Err(e) => Err(MLError::new(ErrorType::DBError, e.to_string().as_str())), 39 ) {
23 }?; 40 Ok(i) => Ok(i),
41 Err(e) => Err(MLError::new(
42 ErrorType::DBError,
43 e.to_string().as_str(),
44 )),
45 }?;
24 46
25 let mut to_download: Vec<String> = vec![]; 47 let mut to_download: Vec<String> = vec![];
26 //(mod_id, version_id) 48 //(mod_id, version_id)
@@ -39,7 +61,10 @@ pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old
39 .ok_or("SOMETHING_HAS_REALLY_GONE_WRONG") 61 .ok_or("SOMETHING_HAS_REALLY_GONE_WRONG")
40 .unwrap(); 62 .unwrap();
41 if &current_version != downloaded_version { 63 if &current_version != downloaded_version {
42 to_disable.push((mod_id.clone(), String::from(downloaded_version))); 64 to_disable.push((
65 mod_id.clone(),
66 String::from(downloaded_version),
67 ));
43 to_download.push(current_version); 68 to_download.push(current_version);
44 } 69 }
45 } 70 }
@@ -54,23 +79,57 @@ pub async fn download(config: Cfg, liststack: Vec<List>, clean: bool, delete_old
54 current_list.clone(), 79 current_list.clone(),
55 config.clone(), 80 config.clone(),
56 get_raw_versions(&config.apis.modrinth, to_download).await, 81 get_raw_versions(&config.apis.modrinth, to_download).await,
82 &mp,
83 &download_p,
57 ) 84 )
58 .await?; 85 .await?;
59 } else { 86 } else {
60 println!("There are no new versions to download"); 87 download_p.println(format!(
88 "There are no new versions to download for {}",
89 current_list.id
90 ));
61 } 91 }
62 92
63 if !to_disable.is_empty() { 93 if !to_disable.is_empty() {
94 let d_p = mp.insert_before(
95 &download_p,
96 ProgressBar::new(to_disable.len().try_into().unwrap()),
97 );
98 d_p.set_style(
99 ProgressStyle::with_template(STYLE_BAR_POS)
100 .unwrap()
101 .progress_chars(PROGRESS_CHARS),
102 );
64 for ver in to_disable { 103 for ver in to_disable {
65 if delete_old { 104 if delete_old {
66 println!("Deleting version {} for mod {}", ver.1, ver.0); 105 d_p.set_message(format!("Delete version {}", ver.1));
67 delete_version(current_list.clone(), ver.1)?; 106 d_p.inc(1);
107 delete_version(&current_list, ver.1)?;
68 } else { 108 } else {
69 disable_version(config.clone(), current_list.clone(), ver.1, ver.0)?; 109 d_p.set_message(format!("Disable version {}", ver.1));
110 d_p.inc(1);
111 disable_version(
112 config,
113 current_list.clone(),
114 ver.1,
115 ver.0,
116 )?;
70 }; 117 };
71 } 118 }
119
120 let del_msg = if delete_old {
121 "Deleted all old versions"
122 } else {
123 "Disabled all old versions"
124 };
125
126 d_p.finish_with_message(del_msg);
72 } 127 }
128
129 download_p.inc(1);
73 } 130 }
74 131
132 download_p.finish_with_message("Downloaded all lists");
133
75 Ok(()) 134 Ok(())
76} 135}
diff --git a/src/commands/io.rs b/src/commands/io.rs
index dd294bc..8e44b2b 100644
--- a/src/commands/io.rs
+++ b/src/commands/io.rs
@@ -1,12 +1,16 @@
1use indicatif::{ProgressBar, ProgressStyle};
1use serde::{Deserialize, Serialize}; 2use serde::{Deserialize, Serialize};
2use std::fs::File; 3use std::fs::File;
3use std::io::prelude::*; 4use std::io::prelude::*;
4 5
5use crate::{ 6use crate::{
6 config::Cfg, 7 config::Cfg,
7 db::{lists_get, lists_get_all_ids, lists_insert, userlist_get_set_version, userlist_get_all_ids, userlist_get_current_version}, 8 db::{
9 lists_get, lists_get_all_ids, lists_insert, userlist_get_all_ids,
10 userlist_get_current_version, userlist_get_set_version,
11 },
8 error::MLE, 12 error::MLE,
9 mod_add, IDSelector, List, Modloader, AddMod, 13 mod_add, AddMod, IDSelector, List, Modloader, STYLE_OPERATION,
10}; 14};
11 15
12#[derive(Debug, Serialize, Deserialize)] 16#[derive(Debug, Serialize, Deserialize)]
@@ -17,14 +21,14 @@ struct Export {
17#[derive(Debug, Serialize, Deserialize)] 21#[derive(Debug, Serialize, Deserialize)]
18struct ExportVersion { 22struct ExportVersion {
19 version: String, 23 version: String,
20 set: bool 24 set: bool,
21} 25}
22 26
23impl ExportVersion { 27impl ExportVersion {
24 fn from(config: Cfg, list_id: &str, mod_id: &str) -> MLE<Self> { 28 fn from(config: &Cfg, list_id: &str, mod_id: &str) -> MLE<Self> {
25 Ok(Self { 29 Ok(Self {
26 version: userlist_get_current_version(config.clone(), list_id, mod_id)?, 30 version: userlist_get_current_version(config, list_id, mod_id)?,
27 set: userlist_get_set_version(config.clone(), list_id, mod_id)? 31 set: userlist_get_set_version(config, list_id, mod_id)?,
28 }) 32 })
29 } 33 }
30} 34}
@@ -39,18 +43,18 @@ struct ExportList {
39} 43}
40 44
41impl ExportList { 45impl ExportList {
42 pub fn from(config: Cfg, list_id: String, download: bool) -> MLE<Self> { 46 pub fn from(config: &Cfg, list_id: &str, download: bool) -> MLE<Self> {
43 let list = lists_get(config.clone(), String::from(&list_id))?; 47 let list = lists_get(config, list_id)?;
44 48
45 let mut dl_folder = None; 49 let mut dl_folder = None;
46 if download { 50 if download {
47 dl_folder = Some(list.download_folder) 51 dl_folder = Some(list.download_folder)
48 }; 52 };
49 53
50 let mods = userlist_get_all_ids(config.clone(), &list_id)?; 54 let mods = userlist_get_all_ids(config, list_id)?;
51 let mut versions = vec![]; 55 let mut versions = vec![];
52 for m in mods { 56 for m in mods {
53 versions.push(ExportVersion::from(config.clone(), &list_id, &m)?) 57 versions.push(ExportVersion::from(config, list_id, &m)?)
54 } 58 }
55 59
56 Ok(Self { 60 Ok(Self {
@@ -63,29 +67,46 @@ impl ExportList {
63 } 67 }
64} 68}
65 69
66pub fn export(config: Cfg, list: Option<String>) -> MLE<()> { 70pub fn export(config: &Cfg, list: Option<String>) -> MLE<()> {
71 let progress = ProgressBar::new_spinner();
72 progress.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
73
67 let mut list_ids: Vec<String> = vec![]; 74 let mut list_ids: Vec<String> = vec![];
68 if list.is_none() { 75 if list.is_none() {
69 list_ids = lists_get_all_ids(config.clone())?; 76 list_ids = lists_get_all_ids(config)?;
70 } else { 77 } else {
71 list_ids.push(lists_get(config.clone(), list.unwrap())?.id); 78 list_ids.push(lists_get(config, &list.unwrap())?.id);
72 } 79 }
80
73 let mut lists: Vec<ExportList> = vec![]; 81 let mut lists: Vec<ExportList> = vec![];
74 for list_id in list_ids { 82 for list_id in list_ids {
75 lists.push(ExportList::from(config.clone(), list_id, true)?); 83 progress.set_message(format!("Export {}", list_id));
84 //TODO download option/ new download on import
85 lists.push(ExportList::from(config, &list_id, true)?);
76 } 86 }
77 87
78 let toml = toml::to_string(&Export { lists })?; 88 let toml = toml::to_string(&Export { lists })?;
79 89
80 let filestr = dirs::home_dir().unwrap().join("mlexport.toml"); 90 let filestr = dirs::home_dir()
91 .unwrap()
92 .join("mlexport.toml")
93 .into_os_string()
94 .into_string()
95 .unwrap();
81 96
82 let mut file = File::create(filestr.into_os_string().into_string().unwrap().as_str())?; 97 progress.set_message("Create file");
98 let mut file = File::create(&filestr)?;
83 file.write_all(toml.as_bytes())?; 99 file.write_all(toml.as_bytes())?;
100 progress.finish_with_message(format!("Exported to {}", filestr));
84 101
85 Ok(()) 102 Ok(())
86} 103}
87 104
88pub async fn import(config: Cfg, file_str: String, direct_download: bool) -> MLE<()> { 105pub async fn import(
106 config: &Cfg,
107 file_str: &str,
108 direct_download: bool,
109) -> MLE<()> {
89 let mut file = File::open(file_str)?; 110 let mut file = File::open(file_str)?;
90 let mut content = String::new(); 111 let mut content = String::new();
91 file.read_to_string(&mut content)?; 112 file.read_to_string(&mut content)?;
@@ -99,18 +120,21 @@ pub async fn import(config: Cfg, file_str: String, direct_download: bool) -> MLE
99 download_folder: exportlist.download_folder.ok_or("NO_DL").unwrap(), 120 download_folder: exportlist.download_folder.ok_or("NO_DL").unwrap(),
100 }; 121 };
101 lists_insert( 122 lists_insert(
102 config.clone(), 123 config,
103 list.id.clone(), 124 &list.id,
104 list.mc_version.clone(), 125 &list.mc_version,
105 list.modloader.clone(), 126 &list.modloader,
106 String::from(&list.download_folder), 127 &list.download_folder,
107 )?; 128 )?;
108 129
109 let mut ver_ids = vec![]; 130 let mut ver_ids = vec![];
110 for id in exportlist.versions { 131 for id in exportlist.versions {
111 ver_ids.push(AddMod { id: IDSelector::VersionID(id.version), set_version: id.set} ); 132 ver_ids.push(AddMod {
133 id: IDSelector::VersionID(id.version),
134 set_version: id.set,
135 });
112 } 136 }
113 mod_add(config.clone(), ver_ids, list, direct_download).await?; 137 mod_add(config, ver_ids, list, direct_download).await?;
114 } 138 }
115 Ok(()) 139 Ok(())
116} 140}
diff --git a/src/commands/list.rs b/src/commands/list.rs
index 4aa4306..3665446 100644
--- a/src/commands/list.rs
+++ b/src/commands/list.rs
@@ -1,11 +1,13 @@
1use indicatif::{ProgressBar, ProgressStyle};
2
1use crate::{ 3use crate::{
2 config::Cfg, 4 config::Cfg,
3 db::{ 5 db::{
4 config_change_current_list, config_get_current_list, lists_get, lists_insert, lists_remove, 6 config_change_current_list, config_get_current_list, lists_get,
5 lists_version, lists_get_all_ids, 7 lists_get_all_ids, lists_insert, lists_remove, lists_version,
6 }, 8 },
7 error::{MLE, MLError, ErrorType}, 9 error::{ErrorType, MLError, MLE},
8 update, Modloader, 10 update, Modloader, STYLE_OPERATION,
9}; 11};
10 12
11#[derive(Debug, Clone, PartialEq, Eq)] 13#[derive(Debug, Clone, PartialEq, Eq)]
@@ -16,31 +18,47 @@ pub struct List {
16 pub download_folder: String, 18 pub download_folder: String,
17} 19}
18 20
19pub fn get_current_list(config: Cfg) -> MLE<List> { 21pub fn get_current_list(config: &Cfg) -> MLE<List> {
20 let id = config_get_current_list(config.clone())?; 22 let id = config_get_current_list(config)?;
21 lists_get(config, id) 23 lists_get(config, &id)
22} 24}
23 25
24pub fn list_add( 26pub fn list_add(
25 config: Cfg, 27 config: &Cfg,
26 id: String, 28 id: &str,
27 mc_version: String, 29 mc_version: &str,
28 modloader: Modloader, 30 modloader: &Modloader,
29 directory: String, 31 directory: &str,
30) -> MLE<()> { 32) -> MLE<()> {
31 lists_insert(config, id, mc_version, modloader, directory) 33 let p = ProgressBar::new_spinner();
34 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
35 p.set_message(format!("Create {}", id));
36 lists_insert(config, id, mc_version, modloader, directory)?;
37 p.finish_with_message(format!("Created {}", id));
38 Ok(())
32} 39}
33 40
34pub fn list_change(config: Cfg, id: String) -> MLE<()> { 41pub fn list_change(config: &Cfg, id: &str) -> MLE<()> {
35 if lists_get_all_ids(config.clone())?.into_iter().find(|l| l == &id).is_none() { 42 let p = ProgressBar::new_spinner();
43 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
44 p.set_message(format!("Change default list to {}", id));
45
46 if !lists_get_all_ids(config)?.into_iter().any(|l| l == id) {
36 return Err(MLError::new(ErrorType::ArgumentError, "List not found")); 47 return Err(MLError::new(ErrorType::ArgumentError, "List not found"));
37 }; 48 };
38 println!("Change default list to: {}", id); 49 config_change_current_list(config, id)?;
39 config_change_current_list(config, id) 50
51 p.finish_with_message(format!("Changed default list to {}", id));
52 Ok(())
40} 53}
41 54
42pub fn list_remove(config: Cfg, id: String) -> MLE<()> { 55pub fn list_remove(config: &Cfg, id: &str) -> MLE<()> {
43 lists_remove(config, id) 56 let p = ProgressBar::new_spinner();
57 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
58 p.set_message(format!("Remove {}", id));
59 lists_remove(config, id)?;
60 p.finish_with_message(format!("Removed {}", id));
61 Ok(())
44} 62}
45 63
46///Changing the current lists version and updating it 64///Changing the current lists version and updating it
@@ -50,31 +68,34 @@ pub fn list_remove(config: Cfg, id: String) -> MLE<()> {
50/// * `config` - The current config 68/// * `config` - The current config
51/// * `args` - All args, to extract the new version 69/// * `args` - All args, to extract the new version
52pub async fn list_version( 70pub async fn list_version(
53 config: Cfg, 71 config: &Cfg,
54 id: String, 72 id: &str,
55 mc_version: String, 73 mc_version: String,
56 download: bool, 74 download: bool,
57 delete: bool, 75 delete: bool,
58) -> MLE<()> { 76) -> MLE<()> {
59 println!( 77 let p = ProgressBar::new_spinner();
78 p.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
79 p.set_message(format!(
60 "Change version for list {} to minecraft version: {}", 80 "Change version for list {} to minecraft version: {}",
61 id, mc_version 81 id, mc_version
62 ); 82 ));
63 83
64 lists_version(config.clone(), &id, &mc_version)?; 84 lists_version(config, id, &mc_version)?;
85
86 p.finish_with_message(format!(
87 "Changed version for list {} to minecraft version: {}",
88 id, mc_version
89 ));
65 90
66 println!( 91 let list = lists_get(config, id)?;
67 "\nCheck for updates for new minecraft version in list {}",
68 id
69 );
70 let list = lists_get(config.clone(), id)?;
71 update(config, vec![list], true, download, delete).await 92 update(config, vec![list], true, download, delete).await
72} 93}
73 94
74pub fn list_list(config: Cfg) -> MLE<()> { 95pub fn list_list(config: &Cfg) -> MLE<()> {
75 let lists = lists_get_all_ids(config.clone())?; 96 let lists = lists_get_all_ids(config)?;
76 for list in lists { 97 for list in lists {
77 let l = lists_get(config.clone(), list)?; 98 let l = lists_get(config, &list)?;
78 println!("{}: | {} | {}", l.id, l.mc_version, l.modloader) 99 println!("{}: | {} | {}", l.id, l.mc_version, l.modloader)
79 } 100 }
80 Ok(()) 101 Ok(())
diff --git a/src/commands/modification.rs b/src/commands/modification.rs
index 9a1a651..4488b70 100644
--- a/src/commands/modification.rs
+++ b/src/commands/modification.rs
@@ -1,24 +1,30 @@
1use std::{io::Write, collections::HashMap}; 1use std::collections::HashMap;
2
3use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2 4
3use crate::{ 5use crate::{
4 config::Cfg, 6 config::Cfg,
5 db::{ 7 db::{
6 lists_get_all_ids, mods_get_id, mods_insert, mods_remove, userlist_get_all_ids, 8 lists_get_all_ids, mods_get_id, mods_get_info, mods_insert,
7 userlist_get_current_version, userlist_insert, userlist_remove, mods_get_info, 9 mods_remove, userlist_get_all_ids, userlist_get_current_version,
10 userlist_insert, userlist_remove,
8 }, 11 },
9 error::{ErrorType, MLError, MLE}, 12 error::{ErrorType, MLError, MLE},
10 files::{delete_version, download_versions}, 13 files::{delete_version, download_versions},
11 modrinth::{extract_current_version, get_raw_versions, project, projects, versions, Version}, 14 modrinth::{
12 List, 15 extract_current_version, get_raw_versions, project, projects, versions,
16 Version,
17 },
18 List, PROGRESS_CHARS, STYLE_BAR_POS, STYLE_OPERATION,
13}; 19};
14 20
15#[derive(Debug, Clone)] 21#[derive(Debug)]
16pub struct AddMod { 22pub struct AddMod {
17 pub id: IDSelector, 23 pub id: IDSelector,
18 pub set_version: bool 24 pub set_version: bool,
19} 25}
20 26
21#[derive(Debug, Clone, PartialEq, Eq)] 27#[derive(Debug, PartialEq, Eq)]
22pub enum IDSelector { 28pub enum IDSelector {
23 ModificationID(String), 29 ModificationID(String),
24 VersionID(String), 30 VersionID(String),
@@ -36,54 +42,76 @@ pub struct ProjectInfo {
36} 42}
37 43
38pub async fn mod_add( 44pub async fn mod_add(
39 config: Cfg, 45 config: &Cfg,
40 mods: Vec<AddMod>, 46 mods: Vec<AddMod>,
41 list: List, 47 list: List,
42 direct_download: bool, 48 direct_download: bool,
43) -> MLE<()> { 49) -> MLE<()> {
44 println!("Add mods to {}", list.id); 50 let mp = MultiProgress::new();
45 println!(" └Add mods:");
46 51
47 let mut mod_ids: Vec<(String, bool)> = Vec::new(); 52 let mut mod_ids: Vec<(String, bool)> = Vec::new();
48 let mut ver_ids: Vec<(String, bool)> = Vec::new(); 53 let mut ver_ids: Vec<(String, bool)> = Vec::new();
49 54
55 let add_p = mp.add(ProgressBar::new(mods.len().try_into().unwrap()));
56 add_p.set_style(
57 ProgressStyle::with_template(STYLE_BAR_POS)
58 .unwrap()
59 .progress_chars(PROGRESS_CHARS),
60 );
61 add_p.set_message("Sort ids");
62
50 //"Sort" project ids from version ids to be able to handle them differently but in a batch 63 //"Sort" project ids from version ids to be able to handle them differently but in a batch
51 for m in mods { 64 for m in mods {
65 add_p.inc(1);
52 match m.id { 66 match m.id {
53 IDSelector::ModificationID(pid) => mod_ids.push((pid, m.set_version)), 67 IDSelector::ModificationID(pid) => {
68 mod_ids.push((pid, m.set_version))
69 }
54 IDSelector::VersionID(vid) => ver_ids.push((vid, m.set_version)), 70 IDSelector::VersionID(vid) => ver_ids.push((vid, m.set_version)),
55 } 71 }
56 } 72 }
57 73
74 add_p.set_message("Get infos");
75
58 let mut projectinfo: Vec<ProjectInfo> = Vec::new(); 76 let mut projectinfo: Vec<ProjectInfo> = Vec::new();
59 if !mod_ids.is_empty() { 77 if !mod_ids.is_empty() {
60 projectinfo.append(&mut get_mod_infos(config.clone(), mod_ids, list.clone()).await?) 78 projectinfo
79 .append(&mut get_mod_infos(config, mod_ids, list.clone()).await?);
61 }; 80 };
62 if !ver_ids.is_empty() { 81 if !ver_ids.is_empty() {
63 projectinfo.append(&mut get_ver_info(config.clone(), ver_ids).await?) 82 projectinfo.append(&mut get_ver_info(config, ver_ids).await?);
64 }; 83 };
65 84
66 if projectinfo.is_empty() { 85 if projectinfo.is_empty() {
67 return Err(MLError::new(ErrorType::ArgumentError, "NO_IDS?")); 86 return Err(MLError::new(ErrorType::ArgumentError, "NO_IDS?"));
68 }; 87 };
69 88
89 add_p.set_message("Add mods to database");
90
70 let mut downloadstack: Vec<Version> = Vec::new(); 91 let mut downloadstack: Vec<Version> = Vec::new();
71 92
72 //Adding each mod to the lists and downloadstack 93 //Adding each mod to the lists and downloadstack
73 if projectinfo.len() == 1 { 94 let project_p = mp.insert_before(
74 println!(" └Insert mod in list {} and save infos", list.id); 95 &add_p,
75 } else { 96 ProgressBar::new(projectinfo.len().try_into().unwrap()),
76 println!(" └Insert mods in list {} and save infos", list.id); 97 );
77 } 98 project_p.set_style(
99 ProgressStyle::with_template(STYLE_BAR_POS)
100 .unwrap()
101 .progress_chars(PROGRESS_CHARS),
102 );
78 103
79 for project in projectinfo { 104 for project in projectinfo {
105 project_p.set_message(format!("Add {}", project.title));
106
80 let current_version_id = if project.current_version.is_none() { 107 let current_version_id = if project.current_version.is_none() {
81 String::from("NONE") 108 String::from("NONE")
82 } else { 109 } else {
83 project.current_version.clone().unwrap().id 110 project.current_version.clone().unwrap().id
84 }; 111 };
112
85 match userlist_insert( 113 match userlist_insert(
86 config.clone(), 114 config,
87 &list.id, 115 &list.id,
88 &project.mod_id, 116 &project.mod_id,
89 &current_version_id, 117 &current_version_id,
@@ -92,7 +120,10 @@ pub async fn mod_add(
92 project.set_version, 120 project.set_version,
93 ) { 121 ) {
94 Err(e) => { 122 Err(e) => {
95 let expected_err = format!("SQL: UNIQUE constraint failed: {}.mod_id", list.id); 123 let expected_err = format!(
124 "SQL: UNIQUE constraint failed: {}.mod_id",
125 list.id
126 );
96 if e.to_string() == expected_err { 127 if e.to_string() == expected_err {
97 Err(MLError::new( 128 Err(MLError::new(
98 ErrorType::ModError, 129 ErrorType::ModError,
@@ -106,7 +137,7 @@ pub async fn mod_add(
106 }?; 137 }?;
107 138
108 match mods_insert( 139 match mods_insert(
109 config.clone(), 140 config,
110 &project.mod_id, 141 &project.mod_id,
111 &project.slug, 142 &project.slug,
112 &project.title, 143 &project.title,
@@ -124,18 +155,35 @@ pub async fn mod_add(
124 if project.current_version.is_some() { 155 if project.current_version.is_some() {
125 downloadstack.push(project.current_version.unwrap()) 156 downloadstack.push(project.current_version.unwrap())
126 }; 157 };
158
159 project_p.inc(1);
127 } 160 }
128 161
162 project_p.finish_with_message("Added all mods to the database");
163
129 //Download all the added mods 164 //Download all the added mods
130 if direct_download { 165 if direct_download {
131 download_versions(list.clone(), config.clone(), downloadstack).await?; 166 add_p.set_message("Download mods");
167 download_versions(
168 list.clone(),
169 config.clone(),
170 downloadstack,
171 &mp,
172 &add_p,
173 )
174 .await?;
132 }; 175 };
133 176
177 add_p.finish_with_message("Added all mods");
178
134 Ok(()) 179 Ok(())
135} 180}
136 181
137async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) -> MLE<Vec<ProjectInfo>> { 182async fn get_mod_infos(
138 183 config: &Cfg,
184 mod_ids: Vec<(String, bool)>,
185 list: List,
186) -> MLE<Vec<ProjectInfo>> {
139 let mut setmap: HashMap<String, bool> = HashMap::new(); 187 let mut setmap: HashMap<String, bool> = HashMap::new();
140 188
141 let mut ids = vec![]; 189 let mut ids = vec![];
@@ -154,8 +202,6 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
154 _ => panic!("PANIC"), 202 _ => panic!("PANIC"),
155 }; 203 };
156 for project in m_projects { 204 for project in m_projects {
157 println!("\t└{}", project.title);
158 println!("\t └Get versions");
159 let available_versions = versions( 205 let available_versions = versions(
160 &config.apis.modrinth, 206 &config.apis.modrinth,
161 String::from(&project.id), 207 String::from(&project.id),
@@ -167,8 +213,8 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
167 let current_version: Option<Version>; 213 let current_version: Option<Version>;
168 let file: String; 214 let file: String;
169 if !available_versions.is_empty() { 215 if !available_versions.is_empty() {
170 let current_id = extract_current_version(available_versions.clone())?; 216 let current_id =
171 println!("\t └Current version: {}", current_id); 217 extract_current_version(available_versions.clone())?;
172 218
173 current_version = Some( 219 current_version = Some(
174 available_versions 220 available_versions
@@ -177,19 +223,15 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
177 .find(|v| v.id == current_id) 223 .find(|v| v.id == current_id)
178 .unwrap(), 224 .unwrap(),
179 ); 225 );
180 226
181 // match primary, if none? 227 // match primary, if none?
182 let files = current_version 228 let files = current_version.clone().ok_or("").unwrap().files;
183 .clone()
184 .ok_or("")
185 .unwrap()
186 .files;
187 229
188 file = match files.clone().into_iter().find(|f| f.primary) { 230 file = match files.clone().into_iter().find(|f| f.primary) {
189 Some(f) => f, 231 Some(f) => f,
190 None => { files[0].clone() } 232 None => files[0].clone(),
191 } 233 }
192 .url; 234 .url;
193 235
194 for ver in available_versions { 236 for ver in available_versions {
195 available_versions_vec.push(ver.id); 237 available_versions_vec.push(ver.id);
@@ -197,15 +239,14 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
197 239
198 projectinfo.push(ProjectInfo { 240 projectinfo.push(ProjectInfo {
199 mod_id: String::from(&project.id), 241 mod_id: String::from(&project.id),
200 slug: project.slug, 242 slug: project.slug.clone(),
201 title: project.title, 243 title: project.title,
202 current_version, 244 current_version,
203 applicable_versions: available_versions_vec, 245 applicable_versions: available_versions_vec,
204 download_link: file, 246 download_link: file,
205 set_version: setmap.get(&project.id).unwrap().clone(), 247 set_version: *setmap.get(&project.slug).unwrap(),
206 }) 248 })
207 } else { 249 } else {
208 println!("\t └There's currently no mod version for your specified target");
209 current_version = None; 250 current_version = None;
210 file = String::from("NONE"); 251 file = String::from("NONE");
211 available_versions_vec.push(String::from("NONE")); 252 available_versions_vec.push(String::from("NONE"));
@@ -216,7 +257,7 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
216 current_version, 257 current_version,
217 applicable_versions: available_versions_vec, 258 applicable_versions: available_versions_vec,
218 download_link: file, 259 download_link: file,
219 set_version: setmap.get(&project.id).unwrap().clone(), 260 set_version: *setmap.get(&project.id).unwrap(),
220 }) 261 })
221 } 262 }
222 } 263 }
@@ -224,8 +265,10 @@ async fn get_mod_infos(config: Cfg, mod_ids: Vec<(String, bool)>, list: List) ->
224 Ok(projectinfo) 265 Ok(projectinfo)
225} 266}
226 267
227async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<ProjectInfo>> { 268async fn get_ver_info(
228 269 config: &Cfg,
270 ver_ids: Vec<(String, bool)>,
271) -> MLE<Vec<ProjectInfo>> {
229 let mut setmap: HashMap<String, bool> = HashMap::new(); 272 let mut setmap: HashMap<String, bool> = HashMap::new();
230 273
231 let mut ids = vec![]; 274 let mut ids = vec![];
@@ -248,14 +291,15 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
248 291
249 for (i, project) in v_projects.into_iter().enumerate() { 292 for (i, project) in v_projects.into_iter().enumerate() {
250 let version = &v_versions[i]; 293 let version = &v_versions[i];
251 println!("\t└{}({})", project.title, version.id); 294
252 let file = version 295 let files = version.clone().files;
253 .clone() 296
254 .files 297 let file = match files.clone().into_iter().find(|f| f.primary) {
255 .into_iter() 298 Some(f) => f,
256 .find(|f| f.primary) 299 None => files[0].clone(),
257 .unwrap() 300 }
258 .url; 301 .url;
302
259 projectinfo.push(ProjectInfo { 303 projectinfo.push(ProjectInfo {
260 mod_id: String::from(&project.id), 304 mod_id: String::from(&project.id),
261 slug: project.slug, 305 slug: project.slug,
@@ -263,7 +307,7 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
263 current_version: Some(version.clone()), 307 current_version: Some(version.clone()),
264 applicable_versions: vec![String::from(&version.id)], 308 applicable_versions: vec![String::from(&version.id)],
265 download_link: file, 309 download_link: file,
266 set_version: setmap.get(&version.id).unwrap().clone(), 310 set_version: *setmap.get(&version.id).unwrap(),
267 }) 311 })
268 } 312 }
269 Ok(projectinfo) 313 Ok(projectinfo)
@@ -275,48 +319,45 @@ async fn get_ver_info(config: Cfg, ver_ids: Vec<(String, bool)>) -> MLE<Vec<Proj
275/// * `config` - config struct 319/// * `config` - config struct
276/// * `id` - name, slug or id of the mod 320/// * `id` - name, slug or id of the mod
277/// * `list` - List struct 321/// * `list` - List struct
278pub fn mod_remove(config: Cfg, id: &str, list: List) -> MLE<()> { 322pub fn mod_remove(config: &Cfg, id: &str, list: &List) -> MLE<()> {
323 let progress = ProgressBar::new_spinner();
324 progress.set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
325
279 let mod_id = mods_get_id(&config.data, id)?; 326 let mod_id = mods_get_id(&config.data, id)?;
280 327
281 println!("Remove mod {} from {}", mods_get_info(config.clone(), &mod_id)?.title, list.id); 328 let info = mods_get_info(config, &mod_id)?;
282 let version = userlist_get_current_version(config.clone(), &list.id, &mod_id)?;
283 329
284 print!(" └Remove from list"); 330 progress.set_message(format!("Remove {} from {}", info.title, list.id));
285 //Force flush of stdout, else print! doesn't print instantly
286 std::io::stdout().flush()?;
287 userlist_remove(config.clone(), &list.id, &mod_id)?;
288 println!(" ✓");
289 331
290 print!(" └Delete file"); 332 let version = userlist_get_current_version(config, &list.id, &mod_id)?;
291 //Force flush of stdout, else print! doesn't print instantly 333
292 std::io::stdout().flush()?; 334 userlist_remove(config, &list.id, &mod_id)?;
335
336 progress.set_message("Delete file");
293 match delete_version(list, version) { 337 match delete_version(list, version) {
294 Ok(_) => (), 338 Ok(_) => (),
295 Err(err) => { 339 Err(err) => {
296 if err.to_string() != "User input not accepted: VERSION_NOT_FOUND_IN_FILES" { 340 if err.to_string()
341 != "User input not accepted: VERSION_NOT_FOUND_IN_FILES"
342 {
297 return Err(err); 343 return Err(err);
298 }; 344 };
299 () 345 }
300 },
301 }; 346 };
302 println!(" ✓");
303 347
304 print!(" └Clean main db table"); 348 progress.set_message("Check main list");
305 //Force flush of stdout, else print! doesn't print instantly 349 let list_ids = lists_get_all_ids(config)?;
306 std::io::stdout().flush()?;
307 let list_ids = lists_get_all_ids(config.clone())?;
308 350
309 // Remove mod from main list if not used elsewhere 351 // Remove mod from main list if not used elsewhere
310 let mut mod_used = false; 352 let mut mod_used = false;
311 for id in list_ids { 353 for id in list_ids {
312 let mods = match userlist_get_all_ids(config.clone(), &id) { 354 let mods = match userlist_get_all_ids(config, &id) {
313 Ok(m) => m, 355 Ok(m) => m,
314 Err(err) => { 356 Err(err) => {
315 if err.to_string() == "Database: NO_MODS_USERLIST" { 357 if err.to_string() == "Database: NO_MODS_USERLIST" {
316 println!(" ✓");
317 return Ok(()); 358 return Ok(());
318 }; 359 };
319 return Err(err) 360 return Err(err);
320 } 361 }
321 }; 362 };
322 if mods.contains(&mod_id) { 363 if mods.contains(&mod_id) {
@@ -326,9 +367,14 @@ pub fn mod_remove(config: Cfg, id: &str, list: List) -> MLE<()> {
326 } 367 }
327 368
328 if !mod_used { 369 if !mod_used {
329 mods_remove(config, mod_id)?; 370 progress.set_message("Remove from main list");
371 mods_remove(config, &mod_id)?;
330 }; 372 };
331 println!(" ✓"); 373
374 progress.finish_with_message(format!(
375 "Removed {} from {}",
376 info.title, list.id
377 ));
332 378
333 Ok(()) 379 Ok(())
334} 380}
diff --git a/src/commands/update.rs b/src/commands/update.rs
index d3a282b..c19c02c 100644
--- a/src/commands/update.rs
+++ b/src/commands/update.rs
@@ -1,49 +1,81 @@
1use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2
1use crate::{ 3use crate::{
2 config::Cfg, 4 config::Cfg,
3 db::{ 5 db::{
4 mods_get_info, userlist_change_versions, userlist_get_all_ids, 6 mods_get_info, userlist_change_versions, userlist_get_all_ids,
5 userlist_get_applicable_versions, userlist_get_current_version, userlist_get_set_version, 7 userlist_get_applicable_versions, userlist_get_current_version,
8 userlist_get_set_version,
6 }, 9 },
7 error::{ErrorType, MLError, MLE}, 10 error::{ErrorType, MLError, MLE},
8 files::{clean_list_dir, delete_version, disable_version, download_versions}, 11 files::{
12 clean_list_dir, delete_version, disable_version, download_versions,
13 },
9 modrinth::{extract_current_version, versions, Version}, 14 modrinth::{extract_current_version, versions, Version},
10 List, 15 List, PROGRESS_CHARS, STYLE_BAR_POS, STYLE_OPERATION,
11}; 16};
12 17
13pub async fn update( 18pub async fn update(
14 config: Cfg, 19 config: &Cfg,
15 liststack: Vec<List>, 20 liststack: Vec<List>,
16 clean: bool, 21 clean: bool,
17 direct_download: bool, 22 direct_download: bool,
18 delete_old: bool, 23 delete_old: bool,
19) -> MLE<()> { 24) -> MLE<()> {
25 let mp = MultiProgress::new();
26
27 let update_p =
28 mp.add(ProgressBar::new(liststack.len().try_into().unwrap()));
29 update_p.set_style(
30 ProgressStyle::with_template(STYLE_BAR_POS)
31 .unwrap()
32 .progress_chars(PROGRESS_CHARS),
33 );
34
20 for current_list in liststack { 35 for current_list in liststack {
21 println!("Update mods in {}", current_list.id); 36 update_p.set_message(format!("Update {}", current_list.id));
22 let mods = userlist_get_all_ids(config.clone(), &current_list.id)?;
23 37
24 let mut current_versions: Vec<(String, String)> = vec![]; 38 let list_p = mp.insert_before(&update_p, ProgressBar::new(2));
39 list_p
40 .set_style(ProgressStyle::with_template(STYLE_OPERATION).unwrap());
41 list_p.set_message("Update mods");
42
43 let mods = userlist_get_all_ids(config, &current_list.id)?;
44
45 let list_u_p = mp.insert_before(
46 &list_p,
47 ProgressBar::new(mods.len().try_into().unwrap()),
48 );
49 list_u_p.set_style(
50 ProgressStyle::with_template(STYLE_BAR_POS)
51 .unwrap()
52 .progress_chars(PROGRESS_CHARS),
53 );
25 54
55 let mut current_versions: Vec<(String, String)> = vec![];
26 let mut updatestack: Vec<Version> = vec![]; 56 let mut updatestack: Vec<Version> = vec![];
27 57
28 for id in mods { 58 for id in mods {
29 let info = mods_get_info(config.clone(), &id)?; 59 let info = mods_get_info(config, &id)?;
30 println!(" {}", info.title); 60 list_u_p.set_message(format!("Update {}", info.title));
31 61
32 if userlist_get_set_version(config.clone(), &current_list.id, &id)? { 62 //Skip check if version is set
33 println!(" │ └Set version, skipping update"); 63 if userlist_get_set_version(config, &current_list.id, &id)? {
64 list_u_p.inc(1);
34 continue; 65 continue;
35 } 66 }
36 67
37 //Getting current installed version for disable or delete 68 //Getting current installed version for disable or delete
38 let disable_version = 69 let disable_version =
39 userlist_get_current_version(config.clone(), &current_list.id, &id)?; 70 userlist_get_current_version(config, &current_list.id, &id)?;
40 71
41 updatestack.push( 72 updatestack.push(
42 match specific_update( 73 match specific_update(
43 config.clone(), 74 config,
44 clean, 75 clean,
45 current_list.clone(), 76 current_list.clone(),
46 String::from(&id), 77 &id,
78 &list_u_p,
47 ) 79 )
48 .await 80 .await
49 { 81 {
@@ -53,46 +85,94 @@ pub async fn update(
53 } 85 }
54 Err(e) => { 86 Err(e) => {
55 if e.to_string() == "Mod: NO_UPDATE_AVAILABLE" { 87 if e.to_string() == "Mod: NO_UPDATE_AVAILABLE" {
56 println!(
57 " │ └No new version found for the specified minecraft version"
58 );
59 } else { 88 } else {
60 return Err(e); 89 return Err(e);
61 }; 90 };
91 list_u_p.inc(1);
62 continue; 92 continue;
63 } 93 }
64 }, 94 },
65 ) 95 );
96 list_u_p.inc(1);
66 } 97 }
67 98
99 list_u_p.finish_with_message(format!(
100 "Updated mods in {}",
101 current_list.id
102 ));
103
68 if clean { 104 if clean {
105 list_p.set_message("Cleaning");
69 clean_list_dir(&current_list)?; 106 clean_list_dir(&current_list)?;
70 }; 107 };
71 108
72 if direct_download && !updatestack.is_empty() { 109 if direct_download && !updatestack.is_empty() {
73 download_versions(current_list.clone(), config.clone(), updatestack).await?; 110 download_versions(
111 current_list.clone(),
112 config.clone(),
113 updatestack,
114 &mp,
115 &list_p,
116 )
117 .await?;
74 118
75 //Disable old versions 119 //Disable old versions
76 if !clean { 120 if !clean {
121 let d_p = mp.insert_before(
122 &list_p,
123 ProgressBar::new(
124 current_versions.len().try_into().unwrap(),
125 ),
126 );
127 d_p.set_style(
128 ProgressStyle::with_template(STYLE_BAR_POS)
129 .unwrap()
130 .progress_chars(PROGRESS_CHARS),
131 );
77 for ver in current_versions { 132 for ver in current_versions {
78 if delete_old { 133 if delete_old {
79 println!(" └Delete version {}", ver.0); 134 d_p.set_message(format!("Delete version {}", ver.0));
80 delete_version(current_list.clone(), ver.0)?; 135 d_p.inc(1);
136 delete_version(&current_list, ver.0)?;
81 } else if ver.0 != "NONE" { 137 } else if ver.0 != "NONE" {
82 println!(" └Disable version {}", ver.0); 138 d_p.set_message(format!("Disable version {}", ver.0));
83 disable_version(config.clone(), current_list.clone(), ver.0, ver.1)?; 139 d_p.inc(1);
140 disable_version(
141 config,
142 current_list.clone(),
143 ver.0,
144 ver.1,
145 )?;
84 }; 146 };
85 } 147 }
148
149 let del_msg = if delete_old {
150 "Deleted all old versions"
151 } else {
152 "Disabled all old versions"
153 };
154
155 d_p.finish_with_message(del_msg);
86 } 156 }
87 }; 157 };
158 list_p.finish_with_message(format!("Updated {}", current_list.id));
159 update_p.inc(1);
88 } 160 }
89 161
162 update_p.finish_with_message("Updated all lists");
163
90 Ok(()) 164 Ok(())
91} 165}
92 166
93async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> MLE<Version> { 167async fn specific_update(
168 config: &Cfg,
169 clean: bool,
170 list: List,
171 id: &str,
172 progress: &ProgressBar,
173) -> MLE<Version> {
94 let applicable_versions = 174 let applicable_versions =
95 versions(&config.apis.modrinth, String::from(&id), list.clone()).await; 175 versions(&config.apis.modrinth, String::from(id), list.clone()).await;
96 176
97 let mut versions: Vec<String> = vec![]; 177 let mut versions: Vec<String> = vec![];
98 178
@@ -108,19 +188,19 @@ async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> ML
108 if clean 188 if clean
109 || (versions.join("|") 189 || (versions.join("|")
110 != userlist_get_applicable_versions( 190 != userlist_get_applicable_versions(
111 config.clone(), 191 config,
112 String::from(&list.id), 192 String::from(&list.id),
113 String::from(&id), 193 String::from(id),
114 )?) 194 )?)
115 { 195 {
116 let current_str = extract_current_version(applicable_versions.clone())?; 196 let current_str = extract_current_version(applicable_versions.clone())?;
117 197
118 if clean { 198 if !clean {
119 println!("\t └Add version to downloadstack"); 199 progress.println(format!(
120 } else { 200 "Found new version for {}",
121 println!("\t └Get versions for specified minecraft versions"); 201 mods_get_info(config, id).unwrap().title
122 println!("\t └New current version: {}", current_str); 202 ));
123 }; 203 }
124 204
125 //get new versions 205 //get new versions
126 let current_ver = match applicable_versions 206 let current_ver = match applicable_versions
@@ -133,73 +213,27 @@ async fn specific_update(config: Cfg, clean: bool, list: List, id: String) -> ML
133 }?; 213 }?;
134 current.push(current_ver.clone()); 214 current.push(current_ver.clone());
135 215
136 let link = match current_ver 216 let files = &current_ver.files;
137 .files 217
138 .into_iter() 218 let link = match files.clone().into_iter().find(|f| f.primary) {
139 .find(|f| f.primary) 219 Some(f) => f,
140 .ok_or("!no primary in links") 220 None => files[0].clone(),
141 { 221 }
142 Ok(p) => Ok(p),
143 Err(e) => Err(MLError::new(ErrorType::Other, e)),
144 }?
145 .url; 222 .url;
146 userlist_change_versions(config, list.id, current_str, versions.join("|"), link, id)?; 223
224 userlist_change_versions(
225 config,
226 list.id,
227 current_str,
228 versions.join("|"),
229 link,
230 id.to_string(),
231 )?;
147 } 232 }
148 233
149 if current.is_empty() { 234 if current.is_empty() {
150 return Err(MLError::new(ErrorType::ModError, "NO_UPDATE_AVAILABLE")); 235 return Err(MLError::new(ErrorType::ModError, "NO_UPDATE_AVAILABLE"));
151 }; 236 };
152 237
153 //println!(" └✔️");
154 Ok(current[0].clone()) 238 Ok(current[0].clone())
155} 239}
156
157// #[tokio::test]
158// async fn download_updates_test() {
159// use crate::{
160// modrinth::{Hash, Version, VersionFile, VersionType},
161// List, Modloader,
162// };
163//
164// let config = Cfg::init().unwrap();
165// let current_list = List {
166// id: String::from("..."),
167// mc_version: String::from("..."),
168// modloader: Modloader::Fabric,
169// download_folder: String::from("./dev/tests/dl"),
170// };
171//
172// let versions = vec![Version {
173// id: "dEqtGnT9".to_string(),
174// project_id: "kYuIpRLv".to_string(),
175// author_id: "Qnt13hO8".to_string(),
176// featured: true,
177// name: "1.2.2-1.19 - Fabric".to_string(),
178// version_number: "1.2.2-1.19".to_string(),
179// changelog: None,
180// date_published: "2022-11-02T17:41:43.072267Z".to_string(),
181// downloads: 58,
182// version_type: VersionType::release,
183// files: vec![VersionFile {
184// hashes: Hash {
185// sha1: "fdc6dc39427fc92cc1d7ad8b275b5b83325e712b".to_string(),
186// sha512: "5b372f00d6e5d6a5ef225c3897826b9f6a2be5506905f7f71b9e939779765b41be6f2a9b029cfc752ad0751d0d2d5f8bb4544408df1363eebdde15641e99a849".to_string()
187// },
188// url: "https://cdn.modrinth.com/data/kYuIpRLv/versions/dEqtGnT9/waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(),
189// filename: "waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(),
190// primary: true,
191// size: 323176
192// }],
193// game_versions: vec![
194// "1.19".to_string(),
195// "1.19.1".to_string(),
196// "1.19.2".to_string()
197// ],
198// loaders: vec![
199// "fabric".to_string()
200// ]
201// }];
202// assert!(download_versions(current_list, config, versions)
203// .await
204// .is_ok())
205// }
diff --git a/src/config.rs b/src/config.rs
index e1049d1..f0eb8f7 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -4,9 +4,12 @@ use std::{
4 path::Path, 4 path::Path,
5}; 5};
6 6
7use indicatif::{ProgressBar, ProgressStyle};
7use serde::{Deserialize, Serialize}; 8use serde::{Deserialize, Serialize};
8 9
9use crate::{db::db_setup, error::MLE, Modloader, VersionLevel, check_game_versions}; 10use crate::{
11 check_game_versions, db::db_setup, error::MLE, Modloader, VersionLevel,
12};
10 13
11#[derive(Debug, Clone, Serialize, Deserialize)] 14#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Cfg { 15pub struct Cfg {
@@ -31,7 +34,7 @@ pub struct Defaults {
31impl Cfg { 34impl Cfg {
32 pub async fn init(path: Option<String>) -> MLE<Self> { 35 pub async fn init(path: Option<String>) -> MLE<Self> {
33 let configfile = match path.clone() { 36 let configfile = match path.clone() {
34 Some(p) => String::from(p), 37 Some(p) => p,
35 None => dirs::config_dir() 38 None => dirs::config_dir()
36 .unwrap() 39 .unwrap()
37 .join("modlist") 40 .join("modlist")
@@ -43,7 +46,8 @@ impl Cfg {
43 let mut file = match File::open(&configfile) { 46 let mut file = match File::open(&configfile) {
44 Ok(file) => file, 47 Ok(file) => file,
45 Err(err) => { 48 Err(err) => {
46 if err.kind() == std::io::ErrorKind::NotFound && path.is_none() { 49 if err.kind() == std::io::ErrorKind::NotFound && path.is_none()
50 {
47 create_config(&configfile)?; 51 create_config(&configfile)?;
48 File::open(&configfile)? 52 File::open(&configfile)?
49 } else { 53 } else {
@@ -71,16 +75,18 @@ impl Cfg {
71 Err(..) => { 75 Err(..) => {
72 create_versions_dummy(&versionfile).await?; 76 create_versions_dummy(&versionfile).await?;
73 check_game_versions(&versionfile, true).await?; 77 check_game_versions(&versionfile, true).await?;
74 }, 78 }
75 } 79 }
80
76 Ok(config) 81 Ok(config)
77 } 82 }
78} 83}
79 84
80fn create_config(path: &str) -> MLE<()> { 85fn create_config(path: &str) -> MLE<()> {
81 print!("No config file found, create default"); 86 let p = ProgressBar::new(1);
82 //Force flush of stdout, else print! doesn't print instantly 87 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
83 std::io::stdout().flush()?; 88 p.set_message("Create default config");
89
84 let cache_dir = dirs::cache_dir() 90 let cache_dir = dirs::cache_dir()
85 .unwrap() 91 .unwrap()
86 .join("modlist") 92 .join("modlist")
@@ -89,10 +95,10 @@ fn create_config(path: &str) -> MLE<()> {
89 let default_cfg = Cfg { 95 let default_cfg = Cfg {
90 data: cache_dir.clone(), 96 data: cache_dir.clone(),
91 cache: format!("{}/cache", cache_dir), 97 cache: format!("{}/cache", cache_dir),
92 versions: cache_dir.clone(), 98 versions: cache_dir,
93 defaults: Defaults { 99 defaults: Defaults {
94 modloader: Modloader::Fabric, 100 modloader: Modloader::Fabric,
95 version: VersionLevel::Release 101 version: VersionLevel::Release,
96 }, 102 },
97 apis: Apis { 103 apis: Apis {
98 modrinth: String::from("https://api.modrinth.com/v2/"), 104 modrinth: String::from("https://api.modrinth.com/v2/"),
@@ -101,37 +107,36 @@ fn create_config(path: &str) -> MLE<()> {
101 create_dir_all(path.split("config.toml").collect::<Vec<&str>>()[0])?; 107 create_dir_all(path.split("config.toml").collect::<Vec<&str>>()[0])?;
102 let mut file = File::create(path)?; 108 let mut file = File::create(path)?;
103 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?; 109 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?;
104 println!(" "); 110 p.finish_with_message(format!("Created default config ({})", path));
105 Ok(()) 111 Ok(())
106} 112}
107 113
108fn create_database(path: &str) -> MLE<()> { 114fn create_database(path: &str) -> MLE<()> {
109 print!("No database found, create base"); 115 let p = ProgressBar::new(1);
110 //Force flush of stdout, else print! doesn't print instantly 116 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
111 std::io::stdout().flush()?; 117 p.set_message("Create database");
112 118
113 File::create(path)?; 119 File::create(path)?;
114 db_setup(path)?; 120 db_setup(path)?;
115 println!(" "); 121 p.finish_with_message(format!("Created database ({})", path));
116 Ok(()) 122 Ok(())
117} 123}
118 124
119fn create_cache(path: &str) -> MLE<()> { 125fn create_cache(path: &str) -> MLE<()> {
120 print!("No cache direcory found, create one"); 126 let p = ProgressBar::new(1);
121 //Force flush of stdout, else print! doesn't print instantly 127 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
122 std::io::stdout().flush()?; 128 p.set_message("Create cache");
123 129
124 create_dir_all(path)?; 130 create_dir_all(path)?;
125 println!(" "); 131 p.finish_with_message(format!("Created cache ({})", path));
126 Ok(()) 132 Ok(())
127} 133}
128 134
129async fn create_versions_dummy(path: &str) -> MLE<()> { 135async fn create_versions_dummy(path: &str) -> MLE<()> {
130 print!("No version file found, create dummy"); 136 let p = ProgressBar::new(1);
131 //Force flush of stdout, else print! doesn't print instantly 137 p.set_style(ProgressStyle::with_template("{wide_msg}").unwrap());
132 std::io::stdout().flush()?; 138 p.set_message("Create version file");
133
134 File::create(path)?; 139 File::create(path)?;
135 println!(" "); 140 p.finish_with_message(format!("Created version file ({})", path));
136 Ok(()) 141 Ok(())
137} 142}
diff --git a/src/db.rs b/src/db.rs
index 8fd21b1..49db2fd 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -9,7 +9,7 @@ use crate::{
9}; 9};
10 10
11//MODS 11//MODS
12pub fn mods_insert(config: Cfg, id: &str, slug: &str, name: &str) -> MLE<()> { 12pub fn mods_insert(config: &Cfg, id: &str, slug: &str, name: &str) -> MLE<()> {
13 let data = format!("{}/data.db", config.data); 13 let data = format!("{}/data.db", config.data);
14 let connection = Connection::open(data)?; 14 let connection = Connection::open(data)?;
15 15
@@ -21,7 +21,9 @@ pub fn mods_insert(config: Cfg, id: &str, slug: &str, name: &str) -> MLE<()> {
21 Ok(()) 21 Ok(())
22} 22}
23 23
24pub fn mods_get_all_ids(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::Error>> { 24pub fn mods_get_all_ids(
25 config: &Cfg,
26) -> Result<Vec<String>, Box<dyn std::error::Error>> {
25 let data = format!("{}/data.db", config.data); 27 let data = format!("{}/data.db", config.data);
26 let connection = Connection::open(data).unwrap(); 28 let connection = Connection::open(data).unwrap();
27 29
@@ -64,8 +66,10 @@ pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
64 } 66 }
65 //get from id if no slug found 67 //get from id if no slug found
66 if mod_id.is_empty() { 68 if mod_id.is_empty() {
67 let mut stmt = connection.prepare("SELECT id FROM mods WHERE id = ?")?; 69 let mut stmt =
68 let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?; 70 connection.prepare("SELECT id FROM mods WHERE id = ?")?;
71 let id_iter =
72 stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
69 73
70 for id in id_iter { 74 for id in id_iter {
71 mod_id = id?; 75 mod_id = id?;
@@ -73,8 +77,10 @@ pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
73 } 77 }
74 //get from title if no id found from slug 78 //get from title if no id found from slug
75 if mod_id.is_empty() { 79 if mod_id.is_empty() {
76 let mut stmt = connection.prepare("SELECT id FROM mods WHERE title = ?")?; 80 let mut stmt =
77 let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?; 81 connection.prepare("SELECT id FROM mods WHERE title = ?")?;
82 let id_iter =
83 stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
78 84
79 for id in id_iter { 85 for id in id_iter {
80 mod_id = id?; 86 mod_id = id?;
@@ -93,12 +99,13 @@ pub struct ModInfo {
93 pub title: String, 99 pub title: String,
94} 100}
95 101
96pub fn mods_get_info(config: Cfg, id: &str) -> MLE<ModInfo> { 102pub fn mods_get_info(config: &Cfg, id: &str) -> MLE<ModInfo> {
97 let data = format!("{}/data.db", config.data); 103 let data = format!("{}/data.db", config.data);
98 let connection = Connection::open(data)?; 104 let connection = Connection::open(data)?;
99 105
100 let mut mod_info: Option<ModInfo> = None; 106 let mut mod_info: Option<ModInfo> = None;
101 let mut stmt = connection.prepare("SELECT title, slug FROM mods WHERE id = ?")?; 107 let mut stmt =
108 connection.prepare("SELECT title, slug FROM mods WHERE id = ?")?;
102 let name_iter = stmt.query_map([id], |row| { 109 let name_iter = stmt.query_map([id], |row| {
103 Ok(vec![ 110 Ok(vec![
104 row.get::<usize, String>(0)?, 111 row.get::<usize, String>(0)?,
@@ -120,9 +127,7 @@ pub fn mods_get_info(config: Cfg, id: &str) -> MLE<ModInfo> {
120 } 127 }
121} 128}
122 129
123pub fn mods_remove(config: Cfg, id: String) -> MLE<()> { 130pub fn mods_remove(config: &Cfg, id: &str) -> MLE<()> {
124 println!("Removing mod {} from database", id);
125
126 let data = format!("{}/data.db", config.data); 131 let data = format!("{}/data.db", config.data);
127 let connection = Connection::open(data)?; 132 let connection = Connection::open(data)?;
128 133
@@ -137,7 +142,10 @@ pub struct DBModlistVersions {
137 pub versions: String, 142 pub versions: String,
138} 143}
139 144
140pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVersions>> { 145pub fn mods_get_versions(
146 config: &Cfg,
147 mods: Vec<String>,
148) -> MLE<Vec<DBModlistVersions>> {
141 let data = format!("{}/data.db", config.data); 149 let data = format!("{}/data.db", config.data);
142 let connection = Connection::open(data)?; 150 let connection = Connection::open(data)?;
143 151
@@ -155,8 +163,9 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
155 } 163 }
156 164
157 let mut versionmaps: Vec<DBModlistVersions> = Vec::new(); 165 let mut versionmaps: Vec<DBModlistVersions> = Vec::new();
158 let mut stmt = connection 166 let mut stmt = connection.prepare(
159 .prepare(format!("SELECT id, versions, title FROM mods {}", wherestr).as_str())?; 167 format!("SELECT id, versions, title FROM mods {}", wherestr).as_str(),
168 )?;
160 let id_iter = stmt.query_map([], |row| { 169 let id_iter = stmt.query_map([], |row| {
161 Ok(vec![ 170 Ok(vec![
162 row.get::<usize, String>(0)?, 171 row.get::<usize, String>(0)?,
@@ -167,11 +176,6 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
167 176
168 for ver in id_iter { 177 for ver in id_iter {
169 let version = ver?; 178 let version = ver?;
170 println!(
171 "\t({}) Get versions from the database",
172 String::from(&version[2])
173 );
174 //println!("Found versions {} for mod {}", version[1], version[0]);
175 versionmaps.push(DBModlistVersions { 179 versionmaps.push(DBModlistVersions {
176 mod_id: String::from(&version[0]), 180 mod_id: String::from(&version[0]),
177 versions: String::from(&version[1]), 181 versions: String::from(&version[1]),
@@ -186,7 +190,7 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> MLE<Vec<DBModlistVer
186 190
187//userlist 191//userlist
188pub fn userlist_insert( 192pub fn userlist_insert(
189 config: Cfg, 193 config: &Cfg,
190 list_id: &str, 194 list_id: &str,
191 mod_id: &str, 195 mod_id: &str,
192 current_version: &str, 196 current_version: &str,
@@ -220,26 +224,29 @@ pub fn userlist_insert(
220 Ok(()) 224 Ok(())
221} 225}
222 226
223pub fn userlist_get_all_ids(config: Cfg, list_id: &str) -> MLE<Vec<String>> { 227pub fn userlist_get_all_ids(config: &Cfg, list_id: &str) -> MLE<Vec<String>> {
224 let data = format!("{}/data.db", config.data); 228 let data = format!("{}/data.db", config.data);
225 let connection = Connection::open(data).unwrap(); 229 let connection = Connection::open(data).unwrap();
226 230
227 let mut mod_ids: Vec<String> = Vec::new(); 231 let mut mod_ids: Vec<String> = Vec::new();
228 let mut stmt = connection.prepare(format!("SELECT mod_id FROM {}", list_id).as_str())?; 232 let mut stmt = connection
233 .prepare(format!("SELECT mod_id FROM {}", list_id).as_str())?;
229 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 234 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
230 235
231 for id in id_iter { 236 for id in id_iter {
232 //println!("Found id {:?}", id.as_ref().unwrap());
233 mod_ids.push(id?) 237 mod_ids.push(id?)
234 } 238 }
235 239
236 match mod_ids.is_empty() { 240 match mod_ids.is_empty() {
237 true => Err(MLError::new(ErrorType::DBError, "NO_MODS_USERLIST")), 241 true => Err(MLError::new(
242 ErrorType::DBError,
243 &format!("NO_MODS_USERLIST{}", list_id),
244 )),
238 false => Ok(mod_ids), 245 false => Ok(mod_ids),
239 } 246 }
240} 247}
241 248
242pub fn userlist_remove(config: Cfg, list_id: &str, mod_id: &str) -> MLE<()> { 249pub fn userlist_remove(config: &Cfg, list_id: &str, mod_id: &str) -> MLE<()> {
243 let data = format!("{}/data.db", config.data); 250 let data = format!("{}/data.db", config.data);
244 let connection = Connection::open(data)?; 251 let connection = Connection::open(data)?;
245 252
@@ -251,7 +258,7 @@ pub fn userlist_remove(config: Cfg, list_id: &str, mod_id: &str) -> MLE<()> {
251} 258}
252 259
253pub fn userlist_get_applicable_versions( 260pub fn userlist_get_applicable_versions(
254 config: Cfg, 261 config: &Cfg,
255 list_id: String, 262 list_id: String,
256 mod_id: String, 263 mod_id: String,
257) -> MLE<String> { 264) -> MLE<String> {
@@ -266,7 +273,8 @@ pub fn userlist_get_applicable_versions(
266 ) 273 )
267 .as_str(), 274 .as_str(),
268 )?; 275 )?;
269 let ver_iter = stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?; 276 let ver_iter =
277 stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
270 278
271 for ver in ver_iter { 279 for ver in ver_iter {
272 version = ver?; 280 version = ver?;
@@ -279,15 +287,16 @@ pub fn userlist_get_applicable_versions(
279} 287}
280 288
281pub fn userlist_get_all_applicable_versions_with_mods( 289pub fn userlist_get_all_applicable_versions_with_mods(
282 config: Cfg, 290 config: &Cfg,
283 list_id: String, 291 list_id: String,
284) -> MLE<Vec<(String, String)>> { 292) -> MLE<Vec<(String, String)>> {
285 let data = format!("{}/data.db", config.data); 293 let data = format!("{}/data.db", config.data);
286 let connection = Connection::open(data)?; 294 let connection = Connection::open(data)?;
287 295
288 let mut versions: Vec<(String, String)> = Vec::new(); 296 let mut versions: Vec<(String, String)> = Vec::new();
289 let mut stmt = connection 297 let mut stmt = connection.prepare(
290 .prepare(format!("SELECT mod_id, applicable_versions FROM {}", list_id).as_str())?; 298 format!("SELECT mod_id, applicable_versions FROM {}", list_id).as_str(),
299 )?;
291 let id_iter = stmt.query_map([], |row| { 300 let id_iter = stmt.query_map([], |row| {
292 Ok(vec![ 301 Ok(vec![
293 row.get::<usize, String>(0)?, 302 row.get::<usize, String>(0)?,
@@ -307,14 +316,21 @@ pub fn userlist_get_all_applicable_versions_with_mods(
307 Ok(versions) 316 Ok(versions)
308} 317}
309 318
310pub fn userlist_get_current_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE<String> { 319pub fn userlist_get_current_version(
320 config: &Cfg,
321 list_id: &str,
322 mod_id: &str,
323) -> MLE<String> {
311 let data = format!("{}/data.db", config.data); 324 let data = format!("{}/data.db", config.data);
312 let connection = Connection::open(data).unwrap(); 325 let connection = Connection::open(data).unwrap();
313 326
314 let mut version: String = String::new(); 327 let mut version: String = String::new();
315 let mut stmt = connection 328 let mut stmt = connection.prepare(
316 .prepare(format!("SELECT current_version FROM {} WHERE mod_id = ?", list_id).as_str())?; 329 format!("SELECT current_version FROM {} WHERE mod_id = ?", list_id)
317 let ver_iter = stmt.query_map([&mod_id], |row| row.get::<usize, String>(0))?; 330 .as_str(),
331 )?;
332 let ver_iter =
333 stmt.query_map([&mod_id], |row| row.get::<usize, String>(0))?;
318 334
319 for ver in ver_iter { 335 for ver in ver_iter {
320 version = ver?; 336 version = ver?;
@@ -327,15 +343,15 @@ pub fn userlist_get_current_version(config: Cfg, list_id: &str, mod_id: &str) ->
327} 343}
328 344
329pub fn userlist_get_all_current_version_ids( 345pub fn userlist_get_all_current_version_ids(
330 config: Cfg, 346 config: &Cfg,
331 list_id: String, 347 list_id: String,
332) -> MLE<Vec<String>> { 348) -> MLE<Vec<String>> {
333 let data = format!("{}/data.db", config.data); 349 let data = format!("{}/data.db", config.data);
334 let connection = Connection::open(data)?; 350 let connection = Connection::open(data)?;
335 351
336 let mut versions: Vec<String> = Vec::new(); 352 let mut versions: Vec<String> = Vec::new();
337 let mut stmt = 353 let mut stmt = connection
338 connection.prepare(format!("SELECT current_version FROM {}", list_id).as_str())?; 354 .prepare(format!("SELECT current_version FROM {}", list_id).as_str())?;
339 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 355 let id_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
340 356
341 for id in id_iter { 357 for id in id_iter {
@@ -343,25 +359,23 @@ pub fn userlist_get_all_current_version_ids(
343 } 359 }
344 360
345 if versions.is_empty() { 361 if versions.is_empty() {
346 return Err(MLError::new( 362 return Err(MLError::new(ErrorType::DBError, "NO_MODS_ON_LIST"));
347 ErrorType::DBError,
348 "NO_MODS_ON_LIST",
349 ));
350 }; 363 };
351 364
352 Ok(versions) 365 Ok(versions)
353} 366}
354 367
355pub fn userlist_get_all_current_versions_with_mods( 368pub fn userlist_get_all_current_versions_with_mods(
356 config: Cfg, 369 config: &Cfg,
357 list_id: String, 370 list_id: String,
358) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> { 371) -> Result<Vec<(String, String)>, Box<dyn std::error::Error>> {
359 let data = format!("{}/data.db", config.data); 372 let data = format!("{}/data.db", config.data);
360 let connection = Connection::open(data)?; 373 let connection = Connection::open(data)?;
361 374
362 let mut versions: Vec<(String, String)> = Vec::new(); 375 let mut versions: Vec<(String, String)> = Vec::new();
363 let mut stmt = 376 let mut stmt = connection.prepare(
364 connection.prepare(format!("SELECT mod_id, current_version FROM {}", list_id).as_str())?; 377 format!("SELECT mod_id, current_version FROM {}", list_id).as_str(),
378 )?;
365 let id_iter = stmt.query_map([], |row| { 379 let id_iter = stmt.query_map([], |row| {
366 Ok(vec![ 380 Ok(vec![
367 row.get::<usize, String>(0)?, 381 row.get::<usize, String>(0)?,
@@ -384,14 +398,21 @@ pub fn userlist_get_all_current_versions_with_mods(
384 Ok(versions) 398 Ok(versions)
385} 399}
386 400
387pub fn userlist_get_set_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE<bool> { 401pub fn userlist_get_set_version(
402 config: &Cfg,
403 list_id: &str,
404 mod_id: &str,
405) -> MLE<bool> {
388 let data = format!("{}/data.db", config.data); 406 let data = format!("{}/data.db", config.data);
389 let connection = Connection::open(data).unwrap(); 407 let connection = Connection::open(data).unwrap();
390 408
391 let mut set_version: bool = false; 409 let mut set_version: bool = false;
392 let mut stmt = connection 410 let mut stmt = connection.prepare(
393 .prepare(format!("SELECT set_version FROM {} WHERE mod_id = ?", list_id).as_str())?; 411 format!("SELECT set_version FROM {} WHERE mod_id = ?", list_id)
394 let ver_iter = stmt.query_map([&mod_id], |row| row.get::<usize, bool>(0))?; 412 .as_str(),
413 )?;
414 let ver_iter =
415 stmt.query_map([&mod_id], |row| row.get::<usize, bool>(0))?;
395 416
396 for ver in ver_iter { 417 for ver in ver_iter {
397 set_version = ver?; 418 set_version = ver?;
@@ -401,7 +422,7 @@ pub fn userlist_get_set_version(config: Cfg, list_id: &str, mod_id: &str) -> MLE
401} 422}
402 423
403pub fn userlist_change_versions( 424pub fn userlist_change_versions(
404 config: Cfg, 425 config: &Cfg,
405 list_id: String, 426 list_id: String,
406 current_version: String, 427 current_version: String,
407 versions: String, 428 versions: String,
@@ -416,7 +437,7 @@ pub fn userlist_change_versions(
416} 437}
417 438
418pub fn userlist_add_disabled_versions( 439pub fn userlist_add_disabled_versions(
419 config: Cfg, 440 config: &Cfg,
420 list_id: String, 441 list_id: String,
421 disabled_version: String, 442 disabled_version: String,
422 mod_id: String, 443 mod_id: String,
@@ -424,11 +445,16 @@ pub fn userlist_add_disabled_versions(
424 let data = format!("{}/data.db", config.data); 445 let data = format!("{}/data.db", config.data);
425 let connection = Connection::open(data)?; 446 let connection = Connection::open(data)?;
426 447
427 let currently_disabled_versions = 448 let currently_disabled_versions = userlist_get_disabled_versions(
428 userlist_get_disabled_versions(config, String::from(&list_id), String::from(&mod_id))?; 449 config,
450 String::from(&list_id),
451 String::from(&mod_id),
452 )?;
429 let disabled_versions = match currently_disabled_versions == "NONE" { 453 let disabled_versions = match currently_disabled_versions == "NONE" {
430 true => disabled_version, 454 true => disabled_version,
431 false => format!("{}|{}", currently_disabled_versions, disabled_version), 455 false => {
456 format!("{}|{}", currently_disabled_versions, disabled_version)
457 }
432 }; 458 };
433 459
434 connection.execute( 460 connection.execute(
@@ -442,14 +468,21 @@ pub fn userlist_add_disabled_versions(
442 Ok(()) 468 Ok(())
443} 469}
444 470
445pub fn userlist_get_disabled_versions(config: Cfg, list_id: String, mod_id: String) -> MLE<String> { 471pub fn userlist_get_disabled_versions(
472 config: &Cfg,
473 list_id: String,
474 mod_id: String,
475) -> MLE<String> {
446 let data = format!("{}/data.db", config.data); 476 let data = format!("{}/data.db", config.data);
447 let connection = Connection::open(data).unwrap(); 477 let connection = Connection::open(data).unwrap();
448 478
449 let mut version: String = String::new(); 479 let mut version: String = String::new();
450 let mut stmt = connection 480 let mut stmt = connection.prepare(
451 .prepare(format!("SELECT disabled_versions FROM {} WHERE mod_id = ?", list_id).as_str())?; 481 format!("SELECT disabled_versions FROM {} WHERE mod_id = ?", list_id)
452 let ver_iter = stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?; 482 .as_str(),
483 )?;
484 let ver_iter =
485 stmt.query_map([mod_id], |row| row.get::<usize, String>(0))?;
453 486
454 for ver in ver_iter { 487 for ver in ver_iter {
455 version = ver?; 488 version = ver?;
@@ -462,20 +495,20 @@ pub fn userlist_get_disabled_versions(config: Cfg, list_id: String, mod_id: Stri
462} 495}
463 496
464pub fn userlist_get_all_downloads( 497pub fn userlist_get_all_downloads(
465 config: Cfg, 498 config: &Cfg,
466 list_id: String, 499 list_id: String,
467) -> Result<Vec<String>, Box<dyn std::error::Error>> { 500) -> Result<Vec<String>, Box<dyn std::error::Error>> {
468 let data = format!("{}/data.db", config.data); 501 let data = format!("{}/data.db", config.data);
469 let connection = Connection::open(data).unwrap(); 502 let connection = Connection::open(data).unwrap();
470 503
471 let mut links: Vec<String> = Vec::new(); 504 let mut links: Vec<String> = Vec::new();
472 let mut stmt = 505 let mut stmt = connection.prepare(
473 connection.prepare(format!("SELECT current_download FROM {}", list_id).as_str())?; 506 format!("SELECT current_download FROM {}", list_id).as_str(),
507 )?;
474 let link_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 508 let link_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
475 509
476 for link in link_iter { 510 for link in link_iter {
477 let l = link?; 511 let l = link?;
478 println!("Found link {}", String::from(&l));
479 links.push(l) 512 links.push(l)
480 } 513 }
481 514
@@ -492,32 +525,25 @@ pub fn userlist_get_all_downloads(
492//lists 525//lists
493///Inserts into lists table and creates new table 526///Inserts into lists table and creates new table
494pub fn lists_insert( 527pub fn lists_insert(
495 config: Cfg, 528 config: &Cfg,
496 id: String, 529 id: &str,
497 mc_version: String, 530 mc_version: &str,
498 mod_loader: Modloader, 531 mod_loader: &Modloader,
499 download_folder: String, 532 download_folder: &str,
500) -> MLE<()> { 533) -> MLE<()> {
501 println!("Creating list {}", id);
502
503 let data = format!("{}/data.db", config.data); 534 let data = format!("{}/data.db", config.data);
504 let connection = Connection::open(data)?; 535 let connection = Connection::open(data)?;
505 536
506 connection.execute( 537 connection.execute(
507 "INSERT INTO lists VALUES (?1, ?2, ?3, ?4)", 538 "INSERT INTO lists VALUES (?1, ?2, ?3, ?4)",
508 [ 539 [id, mc_version, &mod_loader.to_string(), download_folder],
509 id.clone(),
510 mc_version,
511 mod_loader.to_string(),
512 download_folder,
513 ],
514 )?; 540 )?;
515 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE', 'set_version' INTEGER, CONSTRAINT {}_PK PRIMARY KEY (mod_id) )", id, id).as_str(), [])?; 541 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE', 'set_version' INTEGER, CONSTRAINT {}_PK PRIMARY KEY (mod_id) )", id, id).as_str(), [])?;
516 542
517 Ok(()) 543 Ok(())
518} 544}
519 545
520pub fn lists_remove(config: Cfg, id: String) -> MLE<()> { 546pub fn lists_remove(config: &Cfg, id: &str) -> MLE<()> {
521 let data = format!("{}/data.db", config.data); 547 let data = format!("{}/data.db", config.data);
522 let connection = Connection::open(data)?; 548 let connection = Connection::open(data)?;
523 549
@@ -526,7 +552,7 @@ pub fn lists_remove(config: Cfg, id: String) -> MLE<()> {
526 Ok(()) 552 Ok(())
527} 553}
528 554
529pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> { 555pub fn lists_get(config: &Cfg, list_id: &str) -> MLE<List> {
530 let data = format!("{}/data.db", config.data); 556 let data = format!("{}/data.db", config.data);
531 let connection = Connection::open(data).unwrap(); 557 let connection = Connection::open(data).unwrap();
532 558
@@ -536,8 +562,9 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
536 modloader: Modloader::Fabric, 562 modloader: Modloader::Fabric,
537 download_folder: String::new(), 563 download_folder: String::new(),
538 }; 564 };
539 let mut stmt = connection 565 let mut stmt = connection.prepare(
540 .prepare("SELECT mc_version, modloader, download_folder FROM lists WHERE id = ?")?; 566 "SELECT mc_version, modloader, download_folder FROM lists WHERE id = ?",
567 )?;
541 568
542 let list_iter = stmt.query_map([&list_id], |row| { 569 let list_iter = stmt.query_map([&list_id], |row| {
543 Ok(vec![ 570 Ok(vec![
@@ -550,7 +577,7 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
550 for l in list_iter { 577 for l in list_iter {
551 let li = l?; 578 let li = l?;
552 list = List { 579 list = List {
553 id: String::from(&list_id), 580 id: list_id.to_string(),
554 mc_version: String::from(&li[0]), 581 mc_version: String::from(&li[0]),
555 modloader: Modloader::from(&li[1])?, 582 modloader: Modloader::from(&li[1])?,
556 download_folder: String::from(&li[2]), 583 download_folder: String::from(&li[2]),
@@ -564,7 +591,7 @@ pub fn lists_get(config: Cfg, list_id: String) -> MLE<List> {
564 Ok(list) 591 Ok(list)
565} 592}
566 593
567pub fn lists_version(config: Cfg, list_id: &str, version: &str) -> MLE<()> { 594pub fn lists_version(config: &Cfg, list_id: &str, version: &str) -> MLE<()> {
568 let data = format!("{}/data.db", config.data); 595 let data = format!("{}/data.db", config.data);
569 let connection = Connection::open(data).unwrap(); 596 let connection = Connection::open(data).unwrap();
570 597
@@ -575,7 +602,7 @@ pub fn lists_version(config: Cfg, list_id: &str, version: &str) -> MLE<()> {
575 Ok(()) 602 Ok(())
576} 603}
577 604
578pub fn lists_get_all_ids(config: Cfg) -> MLE<Vec<String>> { 605pub fn lists_get_all_ids(config: &Cfg) -> MLE<Vec<String>> {
579 let data = format!("{}/data.db", config.data); 606 let data = format!("{}/data.db", config.data);
580 let connection = Connection::open(data).unwrap(); 607 let connection = Connection::open(data).unwrap();
581 608
@@ -594,7 +621,7 @@ pub fn lists_get_all_ids(config: Cfg) -> MLE<Vec<String>> {
594} 621}
595 622
596//config 623//config
597pub fn config_change_current_list(config: Cfg, id: String) -> MLE<()> { 624pub fn config_change_current_list(config: &Cfg, id: &str) -> MLE<()> {
598 let data = format!("{}/data.db", config.data); 625 let data = format!("{}/data.db", config.data);
599 let connection = Connection::open(data)?; 626 let connection = Connection::open(data)?;
600 627
@@ -605,12 +632,13 @@ pub fn config_change_current_list(config: Cfg, id: String) -> MLE<()> {
605 Ok(()) 632 Ok(())
606} 633}
607 634
608pub fn config_get_current_list(config: Cfg) -> MLE<String> { 635pub fn config_get_current_list(config: &Cfg) -> MLE<String> {
609 let data = format!("{}/data.db", config.data); 636 let data = format!("{}/data.db", config.data);
610 let connection = Connection::open(data).unwrap(); 637 let connection = Connection::open(data).unwrap();
611 638
612 let mut list_id = String::new(); 639 let mut list_id = String::new();
613 let mut stmt = connection.prepare("SELECT value FROM user_config WHERE id = 'current_list'")?; 640 let mut stmt = connection
641 .prepare("SELECT value FROM user_config WHERE id = 'current_list'")?;
614 let list_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 642 let list_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
615 643
616 for list in list_iter { 644 for list in list_iter {
@@ -626,7 +654,7 @@ pub fn config_get_current_list(config: Cfg) -> MLE<String> {
626 654
627//SETUP(UPDATES) 655//SETUP(UPDATES)
628pub fn s_userlist_update_download( 656pub fn s_userlist_update_download(
629 config: Cfg, 657 config: &Cfg,
630 list_id: String, 658 list_id: String,
631 mod_id: String, 659 mod_id: String,
632 link: String, 660 link: String,
@@ -645,7 +673,9 @@ pub fn s_userlist_update_download(
645 Ok(()) 673 Ok(())
646} 674}
647 675
648pub fn s_config_create_version(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { 676pub fn s_config_create_version(
677 config: &Cfg,
678) -> Result<(), Box<dyn std::error::Error>> {
649 let data = format!("{}/data.db", config.data); 679 let data = format!("{}/data.db", config.data);
650 let connection = Connection::open(data)?; 680 let connection = Connection::open(data)?;
651 681
@@ -656,7 +686,10 @@ pub fn s_config_create_version(config: Cfg) -> Result<(), Box<dyn std::error::Er
656 Ok(()) 686 Ok(())
657} 687}
658 688
659pub fn s_config_update_version(config: Cfg, ver: String) -> Result<(), Box<dyn std::error::Error>> { 689pub fn s_config_update_version(
690 config: &Cfg,
691 ver: String,
692) -> Result<(), Box<dyn std::error::Error>> {
660 let data = format!("{}/data.db", config.data); 693 let data = format!("{}/data.db", config.data);
661 let connection = Connection::open(data)?; 694 let connection = Connection::open(data)?;
662 695
@@ -667,12 +700,15 @@ pub fn s_config_update_version(config: Cfg, ver: String) -> Result<(), Box<dyn s
667 Ok(()) 700 Ok(())
668} 701}
669 702
670pub fn s_config_get_version(config: Cfg) -> Result<String, Box<dyn std::error::Error>> { 703pub fn s_config_get_version(
704 config: &Cfg,
705) -> Result<String, Box<dyn std::error::Error>> {
671 let data = format!("{}/data.db", config.data); 706 let data = format!("{}/data.db", config.data);
672 let connection = Connection::open(data)?; 707 let connection = Connection::open(data)?;
673 708
674 let mut version: String = String::new(); 709 let mut version: String = String::new();
675 let mut stmt = connection.prepare("SELECT value FROM user_config WHERE id = 'db_version'")?; 710 let mut stmt = connection
711 .prepare("SELECT value FROM user_config WHERE id = 'db_version'")?;
676 let ver_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?; 712 let ver_iter = stmt.query_map([], |row| row.get::<usize, String>(0))?;
677 713
678 for ver in ver_iter { 714 for ver in ver_iter {
@@ -689,7 +725,7 @@ pub fn s_config_get_version(config: Cfg) -> Result<String, Box<dyn std::error::E
689} 725}
690 726
691pub fn s_insert_column( 727pub fn s_insert_column(
692 config: Cfg, 728 config: &Cfg,
693 table: String, 729 table: String,
694 column: String, 730 column: String,
695 c_type: String, 731 c_type: String,
diff --git a/src/error.rs b/src/error.rs
index e6afeaa..a2b37a8 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -34,8 +34,12 @@ impl std::error::Error for MLError {
34impl fmt::Display for MLError { 34impl fmt::Display for MLError {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 match self.etype { 36 match self.etype {
37 ErrorType::ArgumentError => write!(f, "User input not accepted: {}", self.message), 37 ErrorType::ArgumentError => {
38 ErrorType::ArgumentCountError => write!(f, "Too many/too few arguments"), 38 write!(f, "User input not accepted: {}", self.message)
39 }
40 ErrorType::ArgumentCountError => {
41 write!(f, "Too many/too few arguments")
42 }
39 ErrorType::ConfigError => write!(f, "CONFIG"), 43 ErrorType::ConfigError => write!(f, "CONFIG"),
40 ErrorType::DBError => write!(f, "Database: {}", self.message), 44 ErrorType::DBError => write!(f, "Database: {}", self.message),
41 ErrorType::ModError => write!(f, "Mod: {}", self.message), 45 ErrorType::ModError => write!(f, "Mod: {}", self.message),
@@ -106,9 +110,11 @@ impl From<std::io::Error> for MLError {
106 110
107impl From<serde_json::error::Error> for MLError { 111impl From<serde_json::error::Error> for MLError {
108 fn from(value: serde_json::error::Error) -> Self { 112 fn from(value: serde_json::error::Error) -> Self {
109 Self { etype: ErrorType::LibJson, message: value.to_string() } 113 Self {
114 etype: ErrorType::LibJson,
115 message: value.to_string(),
116 }
110 } 117 }
111
112} 118}
113 119
114impl MLError { 120impl MLError {
diff --git a/src/files.rs b/src/files.rs
index a4c128e..3a16c62 100644
--- a/src/files.rs
+++ b/src/files.rs
@@ -1,10 +1,13 @@
1use futures_util::StreamExt; 1use futures_util::StreamExt;
2use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
2use reqwest::Client; 3use reqwest::Client;
3use std::{ 4use std::{
5 cmp::min,
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,
7}; 9};
10use tokio::task::JoinSet;
8 11
9use crate::{ 12use crate::{
10 cache::{copy_cached_version, get_cached_versions}, 13 cache::{copy_cached_version, get_cached_versions},
@@ -12,99 +15,160 @@ use crate::{
12 db::{mods_get_info, userlist_add_disabled_versions}, 15 db::{mods_get_info, userlist_add_disabled_versions},
13 error::{ErrorType, MLError, MLE}, 16 error::{ErrorType, MLError, MLE},
14 modrinth::Version, 17 modrinth::Version,
15 List, 18 List, PROGRESS_CHARS, STYLE_BAR_BYTE, STYLE_BAR_POS, STYLE_SPINNER,
16}; 19};
17 20
18pub async fn download_versions(list: List, config: Cfg, versions: Vec<Version>) -> MLE<String> { 21pub async fn download_versions(
19 let mut cached = get_cached_versions(&config.cache); 22 list: List,
23 config: Cfg,
24 versions: Vec<Version>,
25 progress: &MultiProgress,
26 progress_before: &ProgressBar,
27) -> MLE<()> {
28 let cached = get_cached_versions(&config.cache);
20 29
21 // println!("{:#?}", cached); 30 let mut js = JoinSet::new();
22 31
23 let dl_path = String::from(&list.download_folder); 32 let style_spinner = ProgressStyle::with_template(STYLE_SPINNER).unwrap();
24 33
25 println!(" └Download mods to {}", dl_path); 34 let all = progress.insert_before(
35 progress_before,
36 ProgressBar::new(versions.len().try_into().unwrap()),
37 );
38 all.set_style(
39 ProgressStyle::with_template(STYLE_BAR_POS)
40 .unwrap()
41 .progress_chars(PROGRESS_CHARS),
42 );
43 all.set_message(format!("✓Downloading {}", list.id));
26 44
27 for ver in versions { 45 for ver in versions {
28 let project_info = mods_get_info(config.clone(), &ver.project_id)?; 46 let p = progress.insert_before(&all, ProgressBar::new(1));
29 47 p.set_style(style_spinner.clone());
30 //Check cache if already downloaded 48 js.spawn(download_version(
31 let c = cached.remove(&ver.id); 49 config.clone(),
32 if c.is_some() { 50 list.clone(),
33 print!( 51 ver,
34 "\t└({})Get version {} from cache", 52 cached.clone(),
35 project_info.title, ver.id 53 p,
36 ); 54 ));
37 //Force flush of stdout, else print! doesn't print instantly 55 }
38 std::io::stdout().flush()?; 56
39 copy_cached_version(&c.unwrap(), &dl_path); 57 while js.join_next().await.is_some() {
40 println!(" ✓"); 58 all.inc(1)
41 } else { 59 }
42 print!("\t└({})Download version {}", project_info.title, ver.id); 60
43 //Force flush of stdout, else print! doesn't print instantly 61 all.finish_with_message(format!("✓Downloading {}", list.id));
44 std::io::stdout().flush().unwrap(); 62
45 let files = ver.files; 63 Ok(())
46 let file = match files.clone().into_iter().find(|f| f.primary) { 64}
47 Some(f) => f, 65
48 None => files[0].clone() 66async fn download_version(
49 }; 67 config: Cfg,
50 let mut splitname: Vec<&str> = file.filename.split('.').collect(); 68 list: List,
51 let extension = match splitname.pop().ok_or("") { 69 version: Version,
52 Ok(e) => e, 70 mut cached: HashMap<String, String>,
53 Err(..) => return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION")), 71 progress: ProgressBar,
54 }; 72) -> MLE<()> {
55 let filename = format!( 73 let project_info = mods_get_info(&config, &version.project_id)?;
56 "{}.mr.{}.{}.{}", 74
57 splitname.join("."), 75 let dl_path = String::from(&list.download_folder);
58 ver.project_id, 76
59 ver.id, 77 progress.set_message(format!("{} - {}", project_info.title, version.id));
60 extension 78
61 ); 79 let mut cache_msg = "";
62 download_file( 80 //Check cache if already downloaded
63 file.url, 81 let c = cached.remove(&version.id);
64 list.clone().download_folder, 82 if c.is_some() {
65 filename.clone(), 83 progress.set_message(format!("Get {} from cache", version.id));
66 ) 84 cache_msg = " (cached)";
85 copy_cached_version(&c.unwrap(), &dl_path);
86 } else {
87 let files = version.files;
88 let file = match files.clone().into_iter().find(|f| f.primary) {
89 Some(f) => f,
90 None => files[0].clone(),
91 };
92 let mut splitname: Vec<&str> = file.filename.split('.').collect();
93 let extension = match splitname.pop().ok_or("") {
94 Ok(e) => e,
95 Err(..) => {
96 return Err(MLError::new(ErrorType::Other, "NO_FILE_EXTENSION"))
97 }
98 };
99 let filename = format!(
100 "{}.mr.{}.{}.{}",
101 splitname.join("."),
102 version.project_id,
103 version.id,
104 extension
105 );
106
107 download_file(&file.url, &list.download_folder, &filename, &progress)
67 .await?; 108 .await?;
68 println!(" ✓"); 109
69 //Copy file to cache 110 progress.set_message(format!("Copy {} to cache", version.id));
70 print!("\t └Copy to cache"); 111 let dl_path_file = format!("{}/{}", list.download_folder, filename);
71 //Force flush of stdout, else print! doesn't print instantly 112 let cache_path = format!("{}/{}", &config.cache, filename);
72 std::io::stdout().flush().unwrap(); 113
73 let dl_path_file = format!("{}/{}", list.download_folder, filename); 114 copy(dl_path_file, cache_path)?;
74 let cache_path = format!("{}/{}", &config.clone().cache, filename);
75 // println!("{}:{}", dl_path_file, cache_path);
76 copy(dl_path_file, cache_path)?;
77 println!(" ✓");
78 }
79 } 115 }
80 116
81 Ok(dl_path) 117 progress.finish_with_message(format!(
118 "✓{} - {}{}",
119 project_info.title, version.id, cache_msg
120 ));
121
122 Ok(())
82} 123}
83 124
84async fn download_file(url: String, path: String, name: String) -> MLE<()> { 125async fn download_file(
126 url: &str,
127 path: &str,
128 name: &str,
129 progress: &ProgressBar,
130) -> MLE<()> {
85 let dl_path_file = format!("{}/{}", path, name); 131 let dl_path_file = format!("{}/{}", path, name);
86 let res = Client::new().get(String::from(&url)).send().await?; 132 let res = Client::new().get(url).send().await?;
133
134 let size = res.content_length().expect("Couldn't get content length");
135
136 let style_bar_byte = ProgressStyle::with_template(STYLE_BAR_BYTE)
137 .unwrap()
138 .progress_chars(PROGRESS_CHARS);
139
140 progress.set_length(size);
141 progress.set_style(style_bar_byte);
87 142
88 // download chunks 143 // download chunks
89 let mut file = File::create(&dl_path_file)?; 144 let mut file = File::create(&dl_path_file)?;
90 let mut stream = res.bytes_stream(); 145 let mut stream = res.bytes_stream();
91 146
147 let mut downloaded: u64 = 0;
148
92 while let Some(item) = stream.next().await { 149 while let Some(item) = stream.next().await {
150 // progress.inc(1);
93 let chunk = item?; 151 let chunk = item?;
94 file.write_all(&chunk)?; 152 file.write_all(&chunk)?;
153
154 // Progress bar
155 let new = min(downloaded + (chunk.len() as u64), size);
156 downloaded = new;
157 progress.set_position(new);
158
159 // std::thread::sleep(std::time::Duration::from_millis(100));
95 } 160 }
96 161
97 Ok(()) 162 Ok(())
98} 163}
99 164
100pub fn disable_version( 165pub fn disable_version(
101 config: Cfg, 166 config: &Cfg,
102 current_list: List, 167 current_list: List,
103 versionid: String, 168 versionid: String,
104 mod_id: String, 169 mod_id: String,
105) -> MLE<()> { 170) -> MLE<()> {
106 //println!("Disabling version {} for mod {}", versionid, mod_id); 171 let file = get_file_path(&current_list, String::from(&versionid))?;
107 let file = get_file_path(current_list.clone(), String::from(&versionid))?;
108 let disabled = format!("{}.disabled", file); 172 let disabled = format!("{}.disabled", file);
109 173
110 rename(file, disabled)?; 174 rename(file, disabled)?;
@@ -114,7 +178,7 @@ pub fn disable_version(
114 Ok(()) 178 Ok(())
115} 179}
116 180
117pub fn delete_version(list: List, version: String) -> MLE<()> { 181pub fn delete_version(list: &List, version: String) -> MLE<()> {
118 let file = get_file_path(list, version)?; 182 let file = get_file_path(list, version)?;
119 183
120 remove_file(file)?; 184 remove_file(file)?;
@@ -122,14 +186,16 @@ pub fn delete_version(list: List, version: String) -> MLE<()> {
122 Ok(()) 186 Ok(())
123} 187}
124 188
125pub fn get_file_path(list: List, versionid: String) -> MLE<String> { 189pub fn get_file_path(list: &List, versionid: String) -> MLE<String> {
126 let mut names: HashMap<String, String> = HashMap::new(); 190 let mut names: HashMap<String, String> = HashMap::new();
127 for file in read_dir(list.download_folder)? { 191 for file in read_dir(&list.download_folder)? {
128 let path = file?.path(); 192 let path = file?.path();
129 if path.is_file() { 193 if path.is_file() {
130 let pathstr = match path.to_str().ok_or("") { 194 let pathstr = match path.to_str().ok_or("") {
131 Ok(s) => s, 195 Ok(s) => s,
132 Err(..) => return Err(MLError::new(ErrorType::Other, "INVALID_PATH")), 196 Err(..) => {
197 return Err(MLError::new(ErrorType::Other, "INVALID_PATH"))
198 }
133 }; 199 };
134 let namesplit: Vec<&str> = pathstr.split('.').collect(); 200 let namesplit: Vec<&str> = pathstr.split('.').collect();
135 let ver_id = namesplit[namesplit.len() - 2]; 201 let ver_id = namesplit[namesplit.len() - 2];
@@ -168,7 +234,6 @@ pub fn get_downloaded_versions(list: List) -> MLE<HashMap<String, String>> {
168 234
169pub fn clean_list_dir(list: &List) -> MLE<()> { 235pub fn clean_list_dir(list: &List) -> MLE<()> {
170 let dl_path = &list.download_folder; 236 let dl_path = &list.download_folder;
171 println!(" └Clean directory for: {}", list.id);
172 for entry in std::fs::read_dir(dl_path)? { 237 for entry in std::fs::read_dir(dl_path)? {
173 let entry = entry?; 238 let entry = entry?;
174 std::fs::remove_file(entry.path())?; 239 std::fs::remove_file(entry.path())?;
diff --git a/src/lib.rs b/src/lib.rs
index 1c40ceb..f77befc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,14 +6,28 @@ pub mod db;
6pub mod error; 6pub mod error;
7pub mod files; 7pub mod files;
8 8
9use std::{fmt::Display, fs::{File, remove_file, self}, io::{Write, Read}, time::Duration}; 9use std::{
10 fmt::Display,
11 fs::{self, remove_file, File},
12 io::{Read, Write},
13 time::Duration,
14};
10 15
11pub use apis::*;
12use apis::modrinth::{get_game_versions, GameVersion, GameVersionType}; 16use apis::modrinth::{get_game_versions, GameVersion, GameVersionType};
17pub use apis::*;
13pub use commands::*; 18pub use commands::*;
14use error::{ErrorType, MLError, MLE}; 19use error::{ErrorType, MLError, MLE};
20use indicatif::{ProgressBar, ProgressStyle};
15use serde::{Deserialize, Serialize}; 21use serde::{Deserialize, Serialize};
16 22
23pub static STYLE_BAR_BYTE: &str =
24 "{spinner:.green}{wide_msg}{bytes}/{total_bytes} [{bar:.green/lime}]";
25pub static STYLE_BAR_POS: &str = " {wide_msg}{pos}/{len} [{bar:.green/lime}]";
26pub static STYLE_SPINNER: &str = "{spinner:.green}{wide_msg}";
27pub static STYLE_OPERATION: &str = " {wide_msg}";
28pub static STYLE_MESSAGE: &str = "{wide_msg}";
29pub static PROGRESS_CHARS: &str = "#>-";
30
17#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 31#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
18pub enum Modloader { 32pub enum Modloader {
19 #[serde(rename(serialize = "fabric", deserialize = "fabric"))] 33 #[serde(rename(serialize = "fabric", deserialize = "fabric"))]
@@ -30,7 +44,9 @@ impl Modloader {
30 "forge" => Ok(Modloader::Forge), 44 "forge" => Ok(Modloader::Forge),
31 "fabric" => Ok(Modloader::Fabric), 45 "fabric" => Ok(Modloader::Fabric),
32 "quilt" => Ok(Modloader::Quilt), 46 "quilt" => Ok(Modloader::Quilt),
33 _ => Err(MLError::new(ErrorType::ArgumentError, "UNKNOWN_MODLOADER")), 47 _ => {
48 Err(MLError::new(ErrorType::ArgumentError, "UNKNOWN_MODLOADER"))
49 }
34 } 50 }
35 } 51 }
36} 52}
@@ -51,21 +67,29 @@ pub enum VersionLevel {
51 Release, 67 Release,
52 #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))] 68 #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))]
53 Snapshot, 69 Snapshot,
54 Version(String) 70 Version(String),
55} 71}
56 72
57/// Checks if update needed (time) 73/// Checks if update needed (time)
58/// if yes: get versions, update 74/// if yes: get versions, update
59pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> { 75pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> {
76 let p = ProgressBar::new(1);
77 p.set_style(ProgressStyle::with_template(STYLE_MESSAGE).unwrap());
78 p.set_message("Update minecraft versions");
79
60 let creation_time = fs::metadata(path)?.created()?; 80 let creation_time = fs::metadata(path)?.created()?;
61 if !force && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24) { return Ok(()); } 81 if !force
62 print!("Update minecraft versions"); 82 && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24)
63 std::io::stdout().flush()?; 83 {
84 return Ok(());
85 }
86
64 let versions = get_game_versions().await; 87 let versions = get_game_versions().await;
65 remove_file(path)?; 88 remove_file(path)?;
66 let mut file = File::create(path)?; 89 let mut file = File::create(path)?;
67 file.write_all(&serde_json::to_string_pretty(&versions)?.as_bytes())?; 90 file.write_all(serde_json::to_string_pretty(&versions)?.as_bytes())?;
68 println!(" ✓"); 91
92 p.finish_with_message("Updated minecraft versions");
69 Ok(()) 93 Ok(())
70} 94}
71 95
@@ -79,7 +103,6 @@ pub fn load_game_versions(path: &str) -> MLE<Vec<GameVersion>> {
79} 103}
80 104
81impl VersionLevel { 105impl VersionLevel {
82
83 pub fn from(str: &str) -> Self { 106 pub fn from(str: &str) -> Self {
84 match str { 107 match str {
85 "release" => VersionLevel::Release, 108 "release" => VersionLevel::Release,
@@ -88,29 +111,38 @@ impl VersionLevel {
88 } 111 }
89 } 112 }
90 113
91 pub async fn get(self, versions_path: &str, force_update: bool) -> MLE<String> { 114 pub async fn get(
115 self,
116 versions_path: &str,
117 force_update: bool,
118 ) -> MLE<String> {
92 let path = format!("{}/versions.json", versions_path); 119 let path = format!("{}/versions.json", versions_path);
93 check_game_versions(&path, force_update).await?; 120 check_game_versions(&path, force_update).await?;
94 let mut versions = load_game_versions(&path)?.into_iter(); 121 let mut versions = load_game_versions(&path)?.into_iter();
95 122
96 match self { 123 match self {
97 VersionLevel::Release => { 124 VersionLevel::Release => {
98 let release = versions.find(|ver| ver.version_type == GameVersionType::release).unwrap(); 125 let release = versions
99 println!("{:?}", release); 126 .find(|ver| ver.version_type == GameVersionType::release)
127 .unwrap();
100 Ok(release.version) 128 Ok(release.version)
101 }, 129 }
102 VersionLevel::Snapshot => { 130 VersionLevel::Snapshot => {
103 let snapshot = versions.find(|ver| ver.version_type == GameVersionType::snapshot).unwrap(); 131 let snapshot = versions
104 println!("{:?}", snapshot); 132 .find(|ver| ver.version_type == GameVersionType::snapshot)
133 .unwrap();
105 Ok(snapshot.version) 134 Ok(snapshot.version)
106 }, 135 }
107 VersionLevel::Version(v) => { 136 VersionLevel::Version(v) => {
108 if versions.find(|ver| ver.version == v).is_some() { 137 if versions.any(|ver| ver.version == v) {
109 Ok(v) 138 Ok(v)
110 } else { 139 } else {
111 Err(MLError::new(ErrorType::ConfigError, "unknown minecraft version")) 140 Err(MLError::new(
141 ErrorType::ConfigError,
142 "unknown minecraft version",
143 ))
112 } 144 }
113 }, 145 }
114 } 146 }
115 } 147 }
116} 148}
diff --git a/src/main.rs b/src/main.rs
index 31a320b..5d60a17 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,8 +2,9 @@ use clap::{Parser, Subcommand};
2use modlist::{ 2use modlist::{
3 config::Cfg, 3 config::Cfg,
4 db::{config_get_current_list, lists_get, lists_get_all_ids}, 4 db::{config_get_current_list, lists_get, lists_get_all_ids},
5 download, export, get_current_list, import, list_add, list_change, list_remove, list_version, 5 download, export, get_current_list, import, list_add, list_change,
6 mod_add, mod_remove, update, IDSelector, List, Modloader, VersionLevel, list_list, AddMod, 6 list_list, list_remove, list_version, mod_add, mod_remove, update, AddMod,
7 IDSelector, List, Modloader, VersionLevel,
7}; 8};
8 9
9#[derive(Parser)] 10#[derive(Parser)]
@@ -15,10 +16,6 @@ struct Cli {
15 /// config file path 16 /// config file path
16 #[arg(short, long)] 17 #[arg(short, long)]
17 config: Option<String>, 18 config: Option<String>,
18
19 /// Force GameVersion update
20 #[arg(long)]
21 force_gameupdate: bool,
22} 19}
23 20
24#[derive(Subcommand)] 21#[derive(Subcommand)]
@@ -30,6 +27,10 @@ enum Commands {
30 List { 27 List {
31 #[command(subcommand)] 28 #[command(subcommand)]
32 command: ListCommands, 29 command: ListCommands,
30
31 /// Force GameVersion update
32 #[arg(long)]
33 force_gameupdate: bool,
33 }, 34 },
34 Download { 35 Download {
35 /// download all lists 36 /// download all lists
@@ -43,7 +44,7 @@ enum Commands {
43 /// remove disabled versions 44 /// remove disabled versions
44 #[arg(short, long)] 45 #[arg(short, long)]
45 remove: bool, 46 remove: bool,
46 47
47 /// optional List selection, else default list will be used 48 /// optional List selection, else default list will be used
48 #[arg(short, long)] 49 #[arg(short, long)]
49 list: Option<String>, 50 list: Option<String>,
@@ -81,7 +82,7 @@ enum Commands {
81 /// the list you want to export 82 /// the list you want to export
82 list: Option<String>, 83 list: Option<String>,
83 }, 84 },
84 Test 85 Test,
85} 86}
86 87
87#[derive(Subcommand)] 88#[derive(Subcommand)]
@@ -160,119 +161,134 @@ async fn main() {
160 let config = Cfg::init(cli.config).await.unwrap(); 161 let config = Cfg::init(cli.config).await.unwrap();
161 162
162 match cli.command { 163 match cli.command {
163 Commands::Mod { command } => { 164 Commands::Mod { command } => match command {
164 match command { 165 ModCommands::Add {
165 #[allow(unused_variables)] 166 id,
166 ModCommands::Add { 167 version,
167 id, 168 list,
168 version, 169 download,
169 list, 170 lock,
170 download, 171 } => {
171 lock, 172 let listf = match list {
172 } => { 173 Some(list) => lists_get(&config, &list).unwrap(),
173 let listf = match list { 174 None => lists_get(
174 Some(list) => lists_get(config.clone(), list).unwrap(), 175 &config,
175 None => lists_get( 176 &config_get_current_list(&config).unwrap(),
176 config.clone(), 177 )
177 config_get_current_list(config.clone()).unwrap(), 178 .unwrap(),
178 ) 179 };
179 .unwrap(),
180 };
181 180
182 let marked_id = match version { 181 let marked_id = match version {
183 true => IDSelector::VersionID(id), 182 true => IDSelector::VersionID(id),
184 false => IDSelector::ModificationID(id), 183 false => IDSelector::ModificationID(id),
185 }; 184 };
186 185
187 let add_id = AddMod { id: marked_id, set_version: lock }; 186 let add_id = AddMod {
187 id: marked_id,
188 set_version: lock,
189 };
188 190
189 mod_add(config, vec![add_id], listf, download).await 191 mod_add(&config, vec![add_id], listf, download).await
190 }
191 ModCommands::Remove { id, list } => {
192 let listf = match list {
193 Some(list) => lists_get(config.clone(), list).unwrap(),
194 None => lists_get(
195 config.clone(),
196 config_get_current_list(config.clone()).unwrap(),
197 )
198 .unwrap(),
199 };
200 mod_remove(config, &id, listf)
201 }
202 } 192 }
203 } 193 ModCommands::Remove { id, list } => {
204 Commands::List { command } => { 194 let listf = match list {
205 match command { 195 Some(list) => lists_get(&config, &list).unwrap(),
206 ListCommands::Add { 196 None => lists_get(
207 id, 197 &config,
208 directory, 198 &config_get_current_list(&config).unwrap(),
209 modloader, 199 )
210 version, 200 .unwrap(),
211 } => { 201 };
212 let ml = match modloader { 202 mod_remove(&config, &id, &listf)
213 Some(ml) => Modloader::from(&ml).unwrap(),
214 None => config.clone().defaults.modloader,
215 };
216
217 let versions_path = &config.versions;
218 let ver = match version {
219 Some(ver) => VersionLevel::from(&ver).get(versions_path, cli.force_gameupdate).await.unwrap(),
220 None => config.clone().defaults.version.get(versions_path, cli.force_gameupdate).await.unwrap(),
221 };
222
223 list_add(config, id, ver, ml, directory)
224 }
225 ListCommands::Remove { id } => list_remove(config, id),
226 ListCommands::List => {
227 list_list(config)
228 }
229 ListCommands::Change { id } => list_change(config, id),
230 ListCommands::Version {
231 id,
232 version,
233 download,
234 remove,
235 } => list_version(config, id, version, download, remove).await,
236 } 203 }
237 } 204 },
205 Commands::List {
206 command,
207 force_gameupdate,
208 } => match command {
209 ListCommands::Add {
210 id,
211 directory,
212 modloader,
213 version,
214 } => {
215 let ml = match modloader {
216 Some(ml) => Modloader::from(&ml).unwrap(),
217 None => config.defaults.modloader.clone(),
218 };
219
220 let versions_path = &config.versions;
221 let ver = match version {
222 Some(ver) => VersionLevel::from(&ver)
223 .get(versions_path, force_gameupdate)
224 .await
225 .unwrap(),
226 None => config
227 .defaults
228 .version
229 .clone()
230 .get(versions_path, force_gameupdate)
231 .await
232 .unwrap(),
233 };
234
235 list_add(&config, &id, &ver, &ml, &directory)
236 }
237 ListCommands::Remove { id } => list_remove(&config, &id),
238 ListCommands::List => list_list(&config),
239 ListCommands::Change { id } => list_change(&config, &id),
240 ListCommands::Version {
241 id,
242 version,
243 download,
244 remove,
245 } => list_version(&config, &id, version, download, remove).await,
246 },
238 Commands::Update { 247 Commands::Update {
239 all, 248 all,
240 download, 249 download,
241 clean, 250 clean,
242 remove, 251 remove,
243 list 252 list,
244 } => { 253 } => {
245 let mut liststack: Vec<List> = vec![]; 254 let mut liststack: Vec<List> = vec![];
246 if all { 255 if all {
247 let list_ids = lists_get_all_ids(config.clone()).unwrap(); 256 let list_ids = lists_get_all_ids(&config).unwrap();
248 for id in list_ids { 257 for id in list_ids {
249 liststack.push(lists_get(config.clone(), id).unwrap()); 258 liststack.push(lists_get(&config, &id).unwrap());
250 } 259 }
251 } else { 260 } else {
252 let current = match list { 261 let current = match list {
253 Some(l) => lists_get(config.clone(), l).unwrap(), 262 Some(l) => lists_get(&config, &l).unwrap(),
254 None => get_current_list(config.clone()).unwrap(), 263 None => get_current_list(&config).unwrap(),
255 }; 264 };
256 liststack.push(current) 265 liststack.push(current)
257 } 266 }
258 update(config, liststack, clean, download, remove).await 267
268 update(&config, liststack, clean, download, remove).await
259 } 269 }
260 Commands::Download { all, clean, remove, list } => { 270 Commands::Download {
271 all,
272 clean,
273 remove,
274 list,
275 } => {
261 let mut liststack: Vec<List> = vec![]; 276 let mut liststack: Vec<List> = vec![];
262 if all { 277 if all {
263 let list_ids = lists_get_all_ids(config.clone()).unwrap(); 278 let list_ids = lists_get_all_ids(&config).unwrap();
264 for id in list_ids { 279 for id in list_ids {
265 liststack.push(lists_get(config.clone(), id).unwrap()); 280 liststack.push(lists_get(&config, &id).unwrap());
266 } 281 }
267 } else { 282 } else {
268 let current = match list { 283 let current = match list {
269 Some(l) => lists_get(config.clone(), l).unwrap(), 284 Some(l) => lists_get(&config, &l).unwrap(),
270 None => get_current_list(config.clone()).unwrap(), 285 None => get_current_list(&config).unwrap(),
271 }; 286 };
272 liststack.push(current) 287 liststack.push(current)
273 } 288 }
274 download(config, liststack, clean, remove).await 289
275 }, 290 download(&config, liststack, clean, remove).await
291 }
276 Commands::Import { file, download } => { 292 Commands::Import { file, download } => {
277 let filestr: String = match file { 293 let filestr: String = match file {
278 Some(args) => args, 294 Some(args) => args,
@@ -284,9 +300,9 @@ async fn main() {
284 .unwrap(), 300 .unwrap(),
285 }; 301 };
286 302
287 import(config, filestr, download).await 303 import(&config, &filestr, download).await
288 } 304 }
289 Commands::Export { list } => export(config, list), 305 Commands::Export { list } => export(&config, list),
290 Commands::Test => Ok(()), 306 Commands::Test => Ok(()),
291 } 307 }
292 .unwrap(); 308 .unwrap();