summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml3
-rw-r--r--data.dbbin16384 -> 24576 bytes
-rw-r--r--data.db.cpbin0 -> 24576 bytes
-rw-r--r--planmodlist.xoppbin193806 -> 193938 bytes
-rw-r--r--src/apis/modrinth.rs24
-rw-r--r--src/commands/download.rs17
-rw-r--r--src/commands/modification.rs25
-rw-r--r--src/commands/setup.rs12
-rw-r--r--src/commands/update.rs67
-rw-r--r--src/db.rs114
-rw-r--r--src/files.rs44
-rw-r--r--src/input.rs21
-rw-r--r--src/lib.rs25
14 files changed, 273 insertions, 81 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 29ff9de..99b0f6c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -647,7 +647,7 @@ dependencies = [
647 647
648[[package]] 648[[package]]
649name = "modlist" 649name = "modlist"
650version = "0.2.2" 650version = "0.3.1"
651dependencies = [ 651dependencies = [
652 "chrono", 652 "chrono",
653 "config", 653 "config",
diff --git a/Cargo.toml b/Cargo.toml
index f0f8b55..afa7d38 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "modlist" 2name = "modlist"
3version = "0.2.2" 3version = "0.3.1"
4edition = "2021" 4edition = "2021"
5 5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -12,6 +12,5 @@ serde = { version = "1.0", features = ["derive"] }
12serde_json = "1.0.87" 12serde_json = "1.0.87"
13config = "0.13.2" 13config = "0.13.2"
14rusqlite = { version = "0.28.0", features = ["bundled"] } 14rusqlite = { version = "0.28.0", features = ["bundled"] }
15# sqlite = "0.28.0"
16futures-util = "0.3.14" 15futures-util = "0.3.14"
17chrono = "0.4.22" 16chrono = "0.4.22"
diff --git a/data.db b/data.db
index 7f62a4e..9f6316d 100644
--- a/data.db
+++ b/data.db
Binary files differ
diff --git a/data.db.cp b/data.db.cp
new file mode 100644
index 0000000..7ec6c91
--- /dev/null
+++ b/data.db.cp
Binary files differ
diff --git a/planmodlist.xopp b/planmodlist.xopp
index 5851878..7640c1e 100644
--- a/planmodlist.xopp
+++ b/planmodlist.xopp
Binary files differ
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs
index c99cfbf..7b322cb 100644
--- a/src/apis/modrinth.rs
+++ b/src/apis/modrinth.rs
@@ -1,5 +1,6 @@
1use std::io::{Error, ErrorKind}; 1use std::io::{Error, ErrorKind};
2use chrono::{DateTime, FixedOffset}; 2use chrono::{DateTime, FixedOffset};
3use reqwest::Client;
3use serde::Deserialize; 4use serde::Deserialize;
4 5
5use crate::{Modloader, List}; 6use crate::{Modloader, List};
@@ -113,18 +114,21 @@ pub struct Hash {
113async fn get(api: String, path: String) -> Result<Vec<u8>, Box<dyn std::error::Error>> { 114async fn get(api: String, path: String) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
114 let url = format!(r#"{}{}"#, api, path); 115 let url = format!(r#"{}{}"#, api, path);
115 116
116 dbg!(&url); 117 let client = Client::builder()
117 118 .user_agent(format!("fxqnlr/modlistcli/{} ([email protected])", env!("CARGO_PKG_VERSION")))
118 let data = reqwest::get(url) 119 .build()?;
119 .await? 120 let data = client.get(url)
120 .bytes() 121 .send()
121 .await? 122 .await?
122 .to_vec(); 123 .bytes()
124 .await?
125 .to_vec();
123 126
124 Ok(data) 127 Ok(data)
125} 128}
126 129
127pub async fn project(api: String, name: &str) -> Project { 130pub async fn project(api: String, name: &str) -> Project {
131 println!("!!!PROJECT");
128 let url = format!("project/{}", name); 132 let url = format!("project/{}", name);
129 let data = get(api, url); 133 let data = get(api, url);
130 134
@@ -132,9 +136,11 @@ pub async fn project(api: String, name: &str) -> Project {
132} 136}
133 137
134pub async fn projects(api: String, ids: Vec<String>) -> Vec<Project> { 138pub async fn projects(api: String, ids: Vec<String>) -> Vec<Project> {
139 //println!("!!!PROJECTS");
140 println!("Getting versions for all mods from modrinth");
135 let all = ids.join(r#"",""#); 141 let all = ids.join(r#"",""#);
136 let url = format!(r#"projects?ids=["{}"]"#, all); 142 let url = format!(r#"projects?ids=["{}"]"#, all);
137 println!("{}", url); 143 //println!("{}", url);
138 144
139 let data = get(api, url); 145 let data = get(api, url);
140 146
@@ -142,6 +148,7 @@ pub async fn projects(api: String, ids: Vec<String>) -> Vec<Project> {
142} 148}
143 149
144pub async fn versions(api: String, id: String, list: List) -> Vec<Version> { 150pub async fn versions(api: String, id: String, list: List) -> Vec<Version> {
151 println!("!!!VERSIONS");
145 let loaderstr = match list.modloader { 152 let loaderstr = match list.modloader {
146 Modloader::Forge => String::from("forge"), 153 Modloader::Forge => String::from("forge"),
147 Modloader::Fabric => String::from("fabric"), 154 Modloader::Fabric => String::from("fabric"),
@@ -155,6 +162,7 @@ pub async fn versions(api: String, id: String, list: List) -> Vec<Version> {
155} 162}
156 163
157pub async fn get_raw_versions(api: String, versions: Vec<String>) -> Vec<Version> { 164pub async fn get_raw_versions(api: String, versions: Vec<String>) -> Vec<Version> {
165 println!("!!!RAWVERSIONS");
158 println!("Getting versions {}", &versions.join(", ")); 166 println!("Getting versions {}", &versions.join(", "));
159 167
160 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); 168 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
diff --git a/src/commands/download.rs b/src/commands/download.rs
index 13ba0e1..b0efdc2 100644
--- a/src/commands/download.rs
+++ b/src/commands/download.rs
@@ -1,3 +1,4 @@
1use crate::{modrinth::Version, files::download_file};
1#[allow(unused_imports)] 2#[allow(unused_imports)]
2use crate::{List, get_current_list, config::Cfg, db::userlist_get_all_downloads, input::Input}; 3use crate::{List, get_current_list, config::Cfg, db::userlist_get_all_downloads, input::Input};
3 4
@@ -32,3 +33,19 @@ async fn download_links(_config: Cfg, _input: Input, _current_list: List, _links
32 33
33 Ok(String::new()) 34 Ok(String::new())
34} 35}
36
37pub async fn download_versions(current_list: List, versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> {
38
39 let dl_path = String::from(&current_list.download_folder);
40
41 for ver in versions {
42 let primary_file = ver.files.into_iter().find(|file| file.primary).unwrap();
43 let mut splitname: Vec<&str> = primary_file.filename.split('.').collect();
44 let extension = splitname.pop().ok_or("NO_FILE_EXTENSION")?;
45 let filename = format!("{}.mr{}.{}", splitname.join("."), ver.id, extension);
46 download_file(primary_file.url, current_list.clone().download_folder, filename).await?;
47 }
48
49 Ok(dl_path)
50}
51
diff --git a/src/commands/modification.rs b/src/commands/modification.rs
index 519a0cb..8e39d11 100644
--- a/src/commands/modification.rs
+++ b/src/commands/modification.rs
@@ -1,12 +1,12 @@
1use std::io::{Error, ErrorKind}; 1use std::io::{Error, ErrorKind};
2 2
3use crate::{modrinth::{project, versions, extract_current_version}, config::Cfg, db::{mods_insert, userlist_remove, mods_get_id, userlist_insert, mods_get_all_ids, userlist_get_all_ids}, input::{Input, Subcmd}, get_current_list}; 3use crate::{modrinth::{project, versions, extract_current_version}, config::Cfg, db::{mods_insert, userlist_remove, mods_get_id, userlist_insert, mods_get_all_ids, userlist_get_all_ids}, input::{Input, Subcmd}, get_current_list, download_versions};
4 4
5pub async fn modification(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> { 5pub async fn modification(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> {
6 6
7 match input.subcommand.ok_or("")? { 7 match input.subcommand.as_ref().ok_or("")? {
8 Subcmd::Add => { 8 Subcmd::Add => {
9 add(config, input.args.ok_or("")?).await 9 add(config, input).await
10 }, 10 },
11 Subcmd::Remove => { 11 Subcmd::Remove => {
12 remove(config, input.args.ok_or("")?) 12 remove(config, input.args.ok_or("")?)
@@ -15,23 +15,23 @@ pub async fn modification(config: Cfg, input: Input) -> Result<(), Box<dyn std::
15 } 15 }
16} 16}
17 17
18async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { 18async fn add(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> {
19 //TODO! DO NOT PANIC IF MOD IS ALREADY IN MODS DB 19
20 let args = input.args.ok_or("")?;
21
20 if args.is_empty() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; 22 if args.is_empty() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); };
21 23
22 let current_list = get_current_list(config.clone())?; 24 let current_list = get_current_list(config.clone())?;
23 25
24 let project = project(String::from(&config.apis.modrinth), &args[0]).await; 26 let project = project(String::from(&config.apis.modrinth), &args[0]).await;
25 27
26 if project.versions.is_empty() { panic!("This should never happen"); };
27
28 let available_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), current_list.clone()).await; 28 let available_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), current_list.clone()).await;
29 29
30 let current_id = extract_current_version(available_versions.clone())?; 30 let current_id = extract_current_version(available_versions.clone())?;
31 31
32 let current_version = available_versions.clone().into_iter().find(|v| v.id == current_id).unwrap(); 32 let current_version = available_versions.clone().into_iter().find(|v| v.id == current_id).unwrap();
33 33
34 let file = current_version.files.into_iter().find(|f| f.primary).unwrap().url; 34 let file = current_version.clone().files.into_iter().find(|f| f.primary).unwrap().url;
35 35
36 let mut available_versions_vec: Vec<String> = Vec::new(); 36 let mut available_versions_vec: Vec<String> = Vec::new();
37 for ver in available_versions { 37 for ver in available_versions {
@@ -43,16 +43,16 @@ async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::E
43 if mods.contains(&project.id) { 43 if mods.contains(&project.id) {
44 return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_ON_LIST"))); } 44 return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_ON_LIST"))); }
45 else { 45 else {
46 userlist_insert(config.clone(), current_list.id, String::from(&project.id), current_version.id, available_versions_vec, file)?; 46 userlist_insert(config.clone(), String::from(&current_list.id), String::from(&project.id), String::from(&current_version.id), available_versions_vec, file)?;
47 } 47 }
48 }, 48 },
49 Err(..) => userlist_insert(config.clone(), current_list.id, String::from(&project.id), current_version.id, available_versions_vec, file)?, 49 Err(..) => userlist_insert(config.clone(), String::from(&current_list.id), String::from(&project.id), String::from(&current_version.id), available_versions_vec, file)?,
50 }; 50 };
51 51
52 match mods_get_all_ids(config.clone()) { 52 match mods_get_all_ids(config.clone()) {
53 Ok(mods) => { 53 Ok(mods) => {
54 if mods.contains(&project.id) { 54 if mods.contains(&project.id) {
55 return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_IN_DATABASE"))) 55 //return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_IN_DATABASE")))
56 } else { 56 } else {
57 mods_insert(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?; 57 mods_insert(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?;
58 } 58 }
@@ -61,6 +61,9 @@ async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::E
61 mods_insert(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?; 61 mods_insert(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?;
62 }, 62 },
63 }; 63 };
64
65 if !input.disable_download { download_versions(current_list, vec![current_version]).await?; }
66
64 Ok(()) 67 Ok(())
65} 68}
66 69
diff --git a/src/commands/setup.rs b/src/commands/setup.rs
index be06040..c7f1bed 100644
--- a/src/commands/setup.rs
+++ b/src/commands/setup.rs
@@ -13,6 +13,7 @@ pub async fn setup(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
13 Ok(ver) => { 13 Ok(ver) => {
14 match ver.as_str() { 14 match ver.as_str() {
15 "0.2" => to_03(config)?, 15 "0.2" => to_03(config)?,
16 "0.3" => to_04(config)?,
16 _ => return Err(Box::new(Error::new(ErrorKind::Other, "UNKNOWN_VERSION"))) 17 _ => return Err(Box::new(Error::new(ErrorKind::Other, "UNKNOWN_VERSION")))
17 } 18 }
18 }, 19 },
@@ -33,7 +34,7 @@ async fn to_02(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
33 34
34 for list in lists { 35 for list in lists {
35 println!("Updating {}", list); 36 println!("Updating {}", list);
36 s_insert_column(config.clone(), String::from(&list), String::from("current_download"), String::from("TEXT"))?; 37 s_insert_column(config.clone(), String::from(&list), String::from("current_download"), String::from("TEXT"), None)?;
37 38
38 let full_list = lists_get(config.clone(), String::from(&list))?; 39 let full_list = lists_get(config.clone(), String::from(&list))?;
39 40
@@ -53,6 +54,13 @@ async fn to_02(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
53} 54}
54 55
55fn to_03(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { 56fn to_03(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
56 s_insert_column(config.clone(), String::from("lists"), String::from("download_folder"), String::from("TEXT"))?; 57 s_insert_column(config.clone(), String::from("lists"), String::from("download_folder"), String::from("TEXT"), None)?;
57 s_config_update_version(config, String::from("0.3")) 58 s_config_update_version(config, String::from("0.3"))
58} 59}
60
61fn to_04(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
62 for list_id in lists_get_all_ids(config.clone())? {
63 s_insert_column(config.clone(), list_id, String::from("disabled_versions"), String::from("TEXT"), Some(String::from("NONE")))?;
64 }
65 s_config_update_version(config, String::from("0.4"))
66}
diff --git a/src/commands/update.rs b/src/commands/update.rs
index 42d19aa..c8f0880 100644
--- a/src/commands/update.rs
+++ b/src/commands/update.rs
@@ -1,9 +1,9 @@
1use std::io::{Error, ErrorKind}; 1use std::{io::{Error, ErrorKind}, fs::{rename, remove_file}};
2 2
3use crate::{config::Cfg, modrinth::{projects, Project, versions, extract_current_version, Version}, get_current_list, db::{userlist_get_all_ids, mods_get_versions, userlist_get_applicable_versions, userlist_change_versions, lists_get_all_ids, lists_get}, List, input::Input, download_file}; 3use crate::{config::Cfg, modrinth::{projects, Project, versions, extract_current_version, Version}, get_current_list, db::{userlist_get_all_ids, mods_get_versions, userlist_get_applicable_versions, userlist_change_versions, lists_get_all_ids, lists_get, userlist_get_current_version, userlist_add_disabled_versions, mods_change_versions}, List, input::Input, files::get_file_path, download_versions};
4 4
5pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> { 5pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> {
6 6
7 let mut liststack: Vec<List> = vec![]; 7 let mut liststack: Vec<List> = vec![];
8 if input.all_lists { 8 if input.all_lists {
9 let list_ids = lists_get_all_ids(config.clone())?; 9 let list_ids = lists_get_all_ids(config.clone())?;
@@ -11,18 +11,22 @@ pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error:
11 liststack.push(lists_get(config.clone(), id)?); 11 liststack.push(lists_get(config.clone(), id)?);
12 } 12 }
13 } else { 13 } else {
14 liststack.push(get_current_list(config.clone())?) 14 let current = get_current_list(config.clone())?;
15 println!("Checking for updates of mods in {}", current.id);
16 liststack.push(current)
15 } 17 }
16 18
17 for current_list in liststack { 19 for current_list in liststack {
18 let mods = userlist_get_all_ids(config.clone(), current_list.clone().id)?; 20 let mods = userlist_get_all_ids(config.clone(), current_list.clone().id)?;
21
22 let mut current_versions: Vec<(String, String)> = vec![];
19 23
20 let mut versions = mods_get_versions(config.clone(), mods.clone())?; 24 let mut versions = mods_get_versions(config.clone(), mods.clone())?;
21 versions.sort_by_key(|ver| ver.mod_id.clone()); 25 versions.sort_by_key(|ver| ver.mod_id.clone());
22 26
23 let mut projects = projects(String::from(&config.apis.modrinth), mods).await; 27 let mut projects = projects(String::from(&config.apis.modrinth), mods).await;
24 projects.sort_by_key(|pro| pro.id.clone()); 28 projects.sort_by_key(|pro| pro.id.clone());
25 29
26 let mut updatestack: Vec<Version> = vec![]; 30 let mut updatestack: Vec<Version> = vec![];
27 for (index, project) in projects.into_iter().enumerate() { 31 for (index, project) in projects.into_iter().enumerate() {
28 //Get versions for project and check if they match up 32 //Get versions for project and check if they match up
@@ -30,14 +34,26 @@ pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error:
30 let p_id = String::from(&project.id); 34 let p_id = String::from(&project.id);
31 let v_id = &current_version.mod_id; 35 let v_id = &current_version.mod_id;
32 if &p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "SORTING_ERROR"))) }; 36 if &p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "SORTING_ERROR"))) };
33 37
38 //Getting current installed version for disable or delete
39 let disable_version = userlist_get_current_version(config.clone(), String::from(&current_list.id), String::from(&project.id))?;
40
41 let version_db_string = project.versions.join("|");
34 42
35 //Adding to stack if not the same versions in the list OR if clean == true 43 //Adding to stack if not the same versions in the list OR if clean == true
36 if input.clone().clean || (project.versions.join("|") != current_version.versions) { 44 if input.clone().clean || (version_db_string != current_version.versions) {
37 updatestack.push(match specific_update(config.clone(), input.clone(), current_list.clone(), project.clone()).await { 45 updatestack.push(match specific_update(config.clone(), input.clone(), current_list.clone(), project.clone()).await {
38 Ok(ver) => ver, 46 Ok(ver) => {
47 current_versions.push((disable_version, p_id));
48 ver
49 },
39 //TODO handle errors (only continue on "NO_UPDATE_AVAILABLE") 50 //TODO handle errors (only continue on "NO_UPDATE_AVAILABLE")
40 Err(_) => { println!("({}) No new version found for the specified minecraft version", project.title); continue; }, 51 Err(e) => {
52 //Updating versions in modlist for no repeating version calls
53 mods_change_versions(config.clone(), version_db_string, project.id)?;
54 println!("({}) No new version found for the specified minecraft version({})", project.title, e);
55 continue;
56 },
41 }); 57 });
42 } else { 58 } else {
43 println!("({}) No new version found", project.title); 59 println!("({}) No new version found", project.title);
@@ -53,14 +69,19 @@ pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error:
53 } 69 }
54 } 70 }
55 71
56 if input.direct_download { download_updates(current_list, updatestack).await?; }; 72 if input.direct_download { download_versions(current_list.clone(), updatestack).await?; };
73
74 //Disable old versions
75 for ver in current_versions {
76 if input.delete_old { delete_old(current_list.clone(), ver.0, ver.1)? } else { disable_old(config.clone(), current_list.clone(), ver.0, ver.1)? };
77 }
57 } 78 }
58 79
59 Ok(()) 80 Ok(())
60} 81}
61 82
62async fn specific_update(config: Cfg, input: Input, list: List, project: Project) -> Result<Version, Box<dyn std::error::Error>> { 83async fn specific_update(config: Cfg, input: Input, list: List, project: Project) -> Result<Version, Box<dyn std::error::Error>> {
63 print!("Checking update for '{}' in {}", project.title, list.id); 84 println!("Checking update for '{}' in {}", project.title, list.id);
64 85
65 let applicable_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), list.clone()).await; 86 let applicable_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), list.clone()).await;
66 87
@@ -88,17 +109,25 @@ async fn specific_update(config: Cfg, input: Input, list: List, project: Project
88 Ok(current[0].clone()) 109 Ok(current[0].clone())
89} 110}
90 111
91async fn download_updates(current_list: List, versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { 112fn disable_old(config: Cfg, current_list: List, versionid: String, mod_id: String) -> Result<(), Box<dyn std::error::Error>> {
113 println!("Disabling version {} for mod {}", versionid, mod_id);
114 let file = get_file_path(current_list.clone(), String::from(&versionid))?;
115 let disabled = format!("{}.disabled", file);
92 116
93 let dl_path = String::from(&current_list.download_folder); 117 rename(file, disabled)?;
94 118
95 for ver in versions { 119 userlist_add_disabled_versions(config, current_list.id, versionid, mod_id)?;
96 let primary_file = ver.files.into_iter().find(|file| file.primary).unwrap(); 120
97 download_file(primary_file.url, current_list.clone().download_folder, primary_file.filename).await?; 121 Ok(())
98 } 122}
99 123
124fn delete_old(current_list: List, versionid: String, mod_id: String) -> Result<(), Box<dyn std::error::Error>> {
125 println!("Deleting version {} for mod {}", versionid, mod_id);
126 let file = get_file_path(current_list, String::from(&versionid))?;
127
128 remove_file(file)?;
100 129
101 Ok(dl_path) 130 Ok(())
102} 131}
103 132
104#[tokio::test] 133#[tokio::test]
@@ -138,5 +167,5 @@ async fn download_updates_test() {
138 "fabric".to_string() 167 "fabric".to_string()
139 ] 168 ]
140 }]; 169 }];
141 assert!(download_updates(current_list, versions).await.is_ok()) 170 assert!(download_versions(current_list, versions).await.is_ok())
142} 171}
diff --git a/src/db.rs b/src/db.rs
index 6b1e3ab..542c162 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -32,7 +32,7 @@ pub fn mods_get_all_ids(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::
32 })?; 32 })?;
33 33
34 for id in id_iter { 34 for id in id_iter {
35 println!("Found id {:?}", id.as_ref().unwrap()); 35 //println!("Found id {:?}", id.as_ref().unwrap());
36 mods.push(id?); 36 mods.push(id?);
37 } 37 }
38 38
@@ -53,7 +53,7 @@ pub fn mods_get_id(config: Cfg, name: String) -> Result<String, Box<dyn std::err
53 })?; 53 })?;
54 54
55 for id in id_iter { 55 for id in id_iter {
56 println!("Found id {:?}", id.as_ref().unwrap()); 56 //println!("Found id {:?}", id.as_ref().unwrap());
57 mod_id = id?; 57 mod_id = id?;
58 }; 58 };
59 59
@@ -63,6 +63,38 @@ pub fn mods_get_id(config: Cfg, name: String) -> Result<String, Box<dyn std::err
63 } 63 }
64} 64}
65 65
66pub fn mods_get_name(config: Cfg, id: String) -> Result<String, Box<dyn std::error::Error>> {
67 let data = format!("{}/data.db", config.data);
68 let connection = Connection::open(data)?;
69
70 let mut mod_name = String::new();
71 let mut stmt = connection.prepare("SELECT name FROM mods WHERE id = ?")?;
72 let name_iter = stmt.query_map([id], |row| {
73 row.get::<usize, String>(0)
74 })?;
75
76 for name in name_iter {
77 //println!("Found id {:?}", id.as_ref().unwrap());
78 mod_name = name?;
79 };
80
81 match mod_name.is_empty() {
82 true => Err(Box::new(Error::new(ErrorKind::NotFound, "MOD_NOT_FOUND"))),
83 false => Ok(mod_name),
84 }
85}
86
87pub fn mods_change_versions(config: Cfg, versions: String, mod_id: String) -> Result<(), Box<dyn std::error::Error>> {
88
89 println!("Updating versions for {} with \n {}", mod_id, versions);
90
91 let data = format!("{}/data.db", config.data);
92 let connection = Connection::open(data)?;
93
94 connection.execute("UPDATE mods SET versions = ?1 WHERE id = ?2", [versions, mod_id])?;
95 Ok(())
96}
97
66pub fn mods_remove(config: Cfg, id: String) -> Result<(), Box<dyn std::error::Error>> { 98pub fn mods_remove(config: Cfg, id: String) -> Result<(), Box<dyn std::error::Error>> {
67 99
68 println!("Removing mod {} from database", id); 100 println!("Removing mod {} from database", id);
@@ -95,14 +127,15 @@ pub fn mods_get_versions(config: Cfg, mods: Vec<String>) -> Result<Vec<DBModlist
95 } 127 }
96 128
97 let mut versionmaps: Vec<DBModlistVersions> = Vec::new(); 129 let mut versionmaps: Vec<DBModlistVersions> = Vec::new();
98 let mut stmt = connection.prepare(dbg!(format!("SELECT id, versions FROM mods {}", wherestr).as_str()))?; 130 let mut stmt = connection.prepare(format!("SELECT id, versions, name FROM mods {}", wherestr).as_str())?;
99 let id_iter = stmt.query_map([], |row| { 131 let id_iter = stmt.query_map([], |row| {
100 Ok(vec![row.get::<usize, String>(0)?, row.get::<usize, String>(1)?]) 132 Ok(vec![row.get::<usize, String>(0)?, row.get::<usize, String>(1)?, row.get::<usize, String>(2)?])
101 })?; 133 })?;
102 134
103 for ver in id_iter { 135 for ver in id_iter {
104 let version = ver?; 136 let version = ver?;
105 println!("Found versions {} for mod {}", version[1], version[0]); 137 println!("Getting versions for {} from the database", String::from(&version[2]));
138 //println!("Found versions {} for mod {}", version[1], version[0]);
106 versionmaps.push(DBModlistVersions { mod_id: String::from(&version[0]), versions: String::from(&version[1]) }) 139 versionmaps.push(DBModlistVersions { mod_id: String::from(&version[0]), versions: String::from(&version[1]) })
107 }; 140 };
108 141
@@ -120,7 +153,7 @@ pub fn userlist_insert(config: Cfg, list_id: String, mod_id: String, current_ver
120 let connection = Connection::open(data)?; 153 let connection = Connection::open(data)?;
121 154
122 155
123 connection.execute(format!("INSERT INTO {} VALUES (?1, ?2, ?3, ?4)", list_id).as_str(), [mod_id, current_version, applicable_versions.join("|"), current_link])?; 156 connection.execute(format!("INSERT INTO {} VALUES (?1, ?2, ?3, ?4, 'NONE')", list_id).as_str(), [mod_id, current_version, applicable_versions.join("|"), current_link])?;
124 157
125 Ok(()) 158 Ok(())
126} 159}
@@ -136,7 +169,7 @@ pub fn userlist_get_all_ids(config: Cfg, list_id: String) -> Result<Vec<String>,
136 })?; 169 })?;
137 170
138 for id in id_iter { 171 for id in id_iter {
139 println!("Found id {:?}", id.as_ref().unwrap()); 172 //println!("Found id {:?}", id.as_ref().unwrap());
140 mod_ids.push(id?) 173 mod_ids.push(id?)
141 }; 174 };
142 175
@@ -167,7 +200,26 @@ pub fn userlist_get_applicable_versions(config: Cfg, list_id: String, mod_id: St
167 })?; 200 })?;
168 201
169 for ver in ver_iter { 202 for ver in ver_iter {
170 println!("Found id {:?}", ver); 203 version = ver?;
204 };
205
206 match version.is_empty() {
207 true => Err(Box::new(Error::new(ErrorKind::NotFound, "MOD_NOT_FOUND"))),
208 false => Ok(version),
209 }
210}
211
212pub fn userlist_get_current_version(config: Cfg, list_id: String, mod_id: String) -> Result<String, Box<dyn std::error::Error>> {
213 let data = format!("{}/data.db", config.data);
214 let connection = Connection::open(data).unwrap();
215
216 let mut version: String = String::new();
217 let mut stmt = connection.prepare(format!("SELECT current_version FROM {} WHERE mod_id = ?", list_id).as_str())?;
218 let ver_iter = stmt.query_map([&mod_id], |row| {
219 row.get::<usize, String>(0)
220 })?;
221
222 for ver in ver_iter {
171 version = ver?; 223 version = ver?;
172 }; 224 };
173 225
@@ -204,6 +256,40 @@ pub fn userlist_change_versions(config: Cfg, list_id: String, current_version: S
204 Ok(()) 256 Ok(())
205} 257}
206 258
259pub fn userlist_add_disabled_versions(config: Cfg, list_id: String, disabled_version: String, mod_id: String) -> Result<(), Box<dyn std::error::Error>> {
260 let data = format!("{}/data.db", config.data);
261 let connection = Connection::open(data)?;
262
263 let currently_disabled_versions = userlist_get_disabled_versions(config, String::from(&list_id), String::from(&mod_id))?;
264 let disabled_versions = match currently_disabled_versions == "NONE" {
265 true => disabled_version,
266 false => format!("{}|{}", currently_disabled_versions, disabled_version),
267 };
268
269 connection.execute(format!("UPDATE {} SET disabled_versions = ?1 WHERE mod_id = ?2", list_id).as_str(), [disabled_versions, mod_id])?;
270 Ok(())
271}
272
273pub fn userlist_get_disabled_versions(config:Cfg, list_id: String, mod_id: String) -> Result<String, Box<dyn std::error::Error>> {
274 let data = format!("{}/data.db", config.data);
275 let connection = Connection::open(data).unwrap();
276
277 let mut version: String = String::new();
278 let mut stmt = connection.prepare(format!("SELECT disabled_versions FROM {} WHERE mod_id = ?", list_id).as_str())?;
279 let ver_iter = stmt.query_map([mod_id], |row| {
280 row.get::<usize, String>(0)
281 })?;
282
283 for ver in ver_iter {
284 version = ver?;
285 };
286
287 match version.is_empty() {
288 true => Err(Box::new(Error::new(ErrorKind::NotFound, "MOD_NOT_FOUND"))),
289 false => Ok(version),
290 }
291}
292
207pub fn userlist_get_all_downloads(config: Cfg, list_id: String) -> Result<Vec<String>, Box<dyn std::error::Error>> { 293pub fn userlist_get_all_downloads(config: Cfg, list_id: String) -> Result<Vec<String>, Box<dyn std::error::Error>> {
208 let data = format!("{}/data.db", config.data); 294 let data = format!("{}/data.db", config.data);
209 let connection = Connection::open(data).unwrap(); 295 let connection = Connection::open(data).unwrap();
@@ -233,7 +319,7 @@ pub fn lists_insert(config: Cfg, id: String, mc_version: String, mod_loader: Mod
233 let connection = Connection::open(data)?; 319 let connection = Connection::open(data)?;
234 320
235 connection.execute("INSERT INTO lists VALUES (?1, ?2, ?3, ?4)", [id.clone(), mc_version, mod_loader.stringify(), download_folder])?; 321 connection.execute("INSERT INTO lists VALUES (?1, ?2, ?3, ?4)", [id.clone(), mc_version, mod_loader.stringify(), download_folder])?;
236 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT )", id).as_str(), [])?; 322 connection.execute(format!("CREATE TABLE {}( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT, 'disabled_versions' TEXT DEFAULT 'NONE' )", id).as_str(), [])?;
237 323
238 Ok(()) 324 Ok(())
239} 325}
@@ -360,11 +446,17 @@ pub fn s_config_get_version(config: Cfg) -> Result<String, Box<dyn std::error::E
360 Ok(version) 446 Ok(version)
361} 447}
362 448
363pub fn s_insert_column(config: Cfg, table: String, column: String, c_type: String) -> Result<(), Box<dyn std::error::Error>> { 449pub fn s_insert_column(config: Cfg, table: String, column: String, c_type: String, default: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
364 let data = format!("{}/data.db", config.data); 450 let data = format!("{}/data.db", config.data);
365 let connection = Connection::open(data)?; 451 let connection = Connection::open(data)?;
366 452
367 connection.execute(format!("ALTER TABLE {} ADD '{}' {}", table, column, c_type).as_str(), ())?; 453 let mut sql = format!("ALTER TABLE {} ADD '{}' {}", table, column, c_type);
454
455 if default.is_some() {
456 sql = format!("{} DEFAULT {}", sql, default.unwrap());
457 }
458
459 connection.execute(sql.as_str(), ())?;
368 Ok(()) 460 Ok(())
369} 461}
370 462
diff --git a/src/files.rs b/src/files.rs
new file mode 100644
index 0000000..1c0b13c
--- /dev/null
+++ b/src/files.rs
@@ -0,0 +1,44 @@
1use std::{fs::{File, read_dir}, io::Write, collections::HashMap};
2use futures_util::StreamExt;
3use reqwest::Client;
4
5use crate::List;
6
7pub async fn download_file(url: String, path: String, name: String) -> Result<(), Box<dyn std::error::Error>> {
8 println!("Downloading {}", url);
9 let dl_path_file = format!("{}/{}", path, name);
10 let res = Client::new()
11 .get(String::from(&url))
12 .send()
13 .await?;
14
15 // download chunks
16 let mut file = File::create(String::from(&dl_path_file))?;
17 let mut stream = res.bytes_stream();
18
19 while let Some(item) = stream.next().await {
20 let chunk = item?;
21 file.write_all(&chunk)?;
22 }
23
24 Ok(())
25}
26
27pub fn get_file_path(list: List, versionid: String) -> Result<String, Box<dyn std::error::Error>> {
28 let mut names: HashMap<String, String> = HashMap::new();
29 for file in read_dir(list.download_folder)? {
30 let path = file?.path();
31 if path.is_file() {
32 let pathstr = path.to_str().ok_or("BAH")?;
33 let namesplit: Vec<&str> = pathstr.split('.').collect();
34 let ver_id = namesplit[namesplit.len() - 2];
35 names.insert(String::from(ver_id), String::from(pathstr));
36 }
37 };
38
39 let api_versionid = format!("mr{}", versionid);
40
41 let filename = names.get(&api_versionid).ok_or("VERSION_NOT_FOUND_IN_FILES")?;
42
43 Ok(filename.to_owned())
44}
diff --git a/src/input.rs b/src/input.rs
index 0946971..b2b4f1b 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -10,6 +10,7 @@ pub struct Input {
10 pub all_lists: bool, 10 pub all_lists: bool,
11 pub delete_old: bool, 11 pub delete_old: bool,
12 pub clean: bool, 12 pub clean: bool,
13 pub disable_download: bool,
13} 14}
14 15
15impl Input { 16impl Input {
@@ -20,7 +21,9 @@ impl Input {
20 let mut all_lists = false; 21 let mut all_lists = false;
21 let mut delete_old = false; 22 let mut delete_old = false;
22 let mut clean = false; 23 let mut clean = false;
23 24 let mut disable_download = false;
25
26 let mut toremove: Vec<usize> = vec![];
24 for (i, input) in split.clone().into_iter().enumerate() { 27 for (i, input) in split.clone().into_iter().enumerate() {
25 if input.starts_with("--") { 28 if input.starts_with("--") {
26 match input { 29 match input {
@@ -28,12 +31,17 @@ impl Input {
28 "--all-lists" => all_lists = true, 31 "--all-lists" => all_lists = true,
29 "--delete-old" => delete_old = true, 32 "--delete-old" => delete_old = true,
30 "--clean" => clean = true, 33 "--clean" => clean = true,
34 "--disable-download" => disable_download = true,
31 _ => continue, 35 _ => continue,
32 } 36 }
33 split.remove(i); 37 toremove.push(i)
34 } 38 }
35 } 39 }
36 40
41 for rem in toremove.into_iter().rev() {
42 split.remove(rem);
43 }
44
37 let command = Cmd::from(split.remove(0))?; 45 let command = Cmd::from(split.remove(0))?;
38 let subcommand = match split.is_empty() { 46 let subcommand = match split.is_empty() {
39 false => Some(Subcmd::from(split.remove(0))?), 47 false => Some(Subcmd::from(split.remove(0))?),
@@ -51,7 +59,7 @@ impl Input {
51 } 59 }
52 }; 60 };
53 61
54 Ok(Self { command, subcommand, args, direct_download, all_lists, delete_old, clean }) 62 Ok(Self { command, subcommand, args, direct_download, all_lists, delete_old, clean, disable_download })
55 } 63 }
56} 64}
57 65
@@ -127,6 +135,11 @@ pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
127#[test] 135#[test]
128fn input_from() { 136fn input_from() {
129 let string = "list add test 1.19.2 fabric"; 137 let string = "list add test 1.19.2 fabric";
130 let input = Input{ command: Cmd::List, subcommand: Some(Subcmd::Add), args: Some(vec![String::from("test"), String::from("1.19.2"), String::from("fabric")]), direct_download: false, all_lists: false, clean: false, delete_old: false }; 138 let input = Input{ command: Cmd::List, subcommand: Some(Subcmd::Add), args: Some(vec![String::from("test"), String::from("1.19.2"), String::from("fabric")]), direct_download: false, all_lists: false, clean: false, delete_old: false, disable_download: false };
139 assert_eq!(Input::from(string).unwrap(), input);
140
141 let string = "update --direct-download --delete-old";
142 let input = Input{ command: Cmd::Update, subcommand: None, args: None, direct_download: true, all_lists: false, clean: false, delete_old: true, disable_download: false };
131 assert_eq!(Input::from(string).unwrap(), input); 143 assert_eq!(Input::from(string).unwrap(), input);
144
132} 145}
diff --git a/src/lib.rs b/src/lib.rs
index e4ebf76..971f544 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,13 +4,12 @@ pub mod commands;
4pub mod input; 4pub mod input;
5pub mod db; 5pub mod db;
6pub mod error; 6pub mod error;
7pub mod files;
7 8
8use std::{io::{Error, ErrorKind, Write}, fs::File}; 9use std::io::{Error, ErrorKind};
9 10
10pub use apis::*; 11pub use apis::*;
11pub use commands::*; 12pub use commands::*;
12use futures_util::StreamExt;
13use reqwest::Client;
14 13
15#[derive(Debug, Clone, PartialEq, Eq)] 14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Modloader { 15pub enum Modloader {
@@ -34,23 +33,3 @@ impl Modloader {
34 } 33 }
35 } 34 }
36} 35}
37
38pub async fn download_file(url: String, path: String, name: String) -> Result<(), Box<dyn std::error::Error>> {
39 println!("Downloading {}", url);
40 let dl_path_file = format!("{}/{}", path, name);
41 let res = Client::new()
42 .get(String::from(&url))
43 .send()
44 .await?;
45
46 // download chunks
47 let mut file = File::create(String::from(&dl_path_file))?;
48 let mut stream = res.bytes_stream();
49
50 while let Some(item) = stream.next().await {
51 let chunk = item?;
52 file.write_all(&chunk)?;
53 }
54
55 Ok(())
56}