diff options
-rw-r--r-- | Cargo.lock | 2 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | data.db | bin | 16384 -> 24576 bytes | |||
-rw-r--r-- | data.db.cp | bin | 0 -> 24576 bytes | |||
-rw-r--r-- | planmodlist.xopp | bin | 193806 -> 193938 bytes | |||
-rw-r--r-- | src/apis/modrinth.rs | 24 | ||||
-rw-r--r-- | src/commands/download.rs | 17 | ||||
-rw-r--r-- | src/commands/modification.rs | 25 | ||||
-rw-r--r-- | src/commands/setup.rs | 12 | ||||
-rw-r--r-- | src/commands/update.rs | 67 | ||||
-rw-r--r-- | src/db.rs | 114 | ||||
-rw-r--r-- | src/files.rs | 44 | ||||
-rw-r--r-- | src/input.rs | 21 | ||||
-rw-r--r-- | src/lib.rs | 25 |
14 files changed, 273 insertions, 81 deletions
@@ -647,7 +647,7 @@ dependencies = [ | |||
647 | 647 | ||
648 | [[package]] | 648 | [[package]] |
649 | name = "modlist" | 649 | name = "modlist" |
650 | version = "0.2.2" | 650 | version = "0.3.1" |
651 | dependencies = [ | 651 | dependencies = [ |
652 | "chrono", | 652 | "chrono", |
653 | "config", | 653 | "config", |
@@ -1,6 +1,6 @@ | |||
1 | [package] | 1 | [package] |
2 | name = "modlist" | 2 | name = "modlist" |
3 | version = "0.2.2" | 3 | version = "0.3.1" |
4 | edition = "2021" | 4 | edition = "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"] } | |||
12 | serde_json = "1.0.87" | 12 | serde_json = "1.0.87" |
13 | config = "0.13.2" | 13 | config = "0.13.2" |
14 | rusqlite = { version = "0.28.0", features = ["bundled"] } | 14 | rusqlite = { version = "0.28.0", features = ["bundled"] } |
15 | # sqlite = "0.28.0" | ||
16 | futures-util = "0.3.14" | 15 | futures-util = "0.3.14" |
17 | chrono = "0.4.22" | 16 | chrono = "0.4.22" |
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 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::io::{Error, ErrorKind}; |
2 | use chrono::{DateTime, FixedOffset}; | 2 | use chrono::{DateTime, FixedOffset}; |
3 | use reqwest::Client; | ||
3 | use serde::Deserialize; | 4 | use serde::Deserialize; |
4 | 5 | ||
5 | use crate::{Modloader, List}; | 6 | use crate::{Modloader, List}; |
@@ -113,18 +114,21 @@ pub struct Hash { | |||
113 | async fn get(api: String, path: String) -> Result<Vec<u8>, Box<dyn std::error::Error>> { | 114 | async 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 | ||
127 | pub async fn project(api: String, name: &str) -> Project { | 130 | pub 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 | ||
134 | pub async fn projects(api: String, ids: Vec<String>) -> Vec<Project> { | 138 | pub 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 | ||
144 | pub async fn versions(api: String, id: String, list: List) -> Vec<Version> { | 150 | pub 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 | ||
157 | pub async fn get_raw_versions(api: String, versions: Vec<String>) -> Vec<Version> { | 164 | pub 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 @@ | |||
1 | use crate::{modrinth::Version, files::download_file}; | ||
1 | #[allow(unused_imports)] | 2 | #[allow(unused_imports)] |
2 | use crate::{List, get_current_list, config::Cfg, db::userlist_get_all_downloads, input::Input}; | 3 | use 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 | |||
37 | pub async fn download_versions(current_list: List, versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { | ||
38 | |||
39 | let dl_path = String::from(¤t_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 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::io::{Error, ErrorKind}; |
2 | 2 | ||
3 | use 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}; | 3 | use 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 | ||
5 | pub async fn modification(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> { | 5 | pub 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 | ||
18 | async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { | 18 | async 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(¤t_list.id), String::from(&project.id), String::from(¤t_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(¤t_list.id), String::from(&project.id), String::from(¤t_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 | ||
55 | fn to_03(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { | 56 | fn 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 | |||
61 | fn 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 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::{io::{Error, ErrorKind}, fs::{rename, remove_file}}; |
2 | 2 | ||
3 | use 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}; | 3 | use 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 | ||
5 | pub async fn update(config: Cfg, input: Input) -> Result<(), Box<dyn std::error::Error>> { | 5 | pub 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 = ¤t_version.mod_id; | 35 | let v_id = ¤t_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(¤t_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 | ||
62 | async fn specific_update(config: Cfg, input: Input, list: List, project: Project) -> Result<Version, Box<dyn std::error::Error>> { | 83 | async 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 | ||
91 | async fn download_updates(current_list: List, versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { | 112 | fn 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(¤t_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 | ||
124 | fn 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 | } |
@@ -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 | ||
66 | pub 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 | |||
87 | pub 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 | |||
66 | pub fn mods_remove(config: Cfg, id: String) -> Result<(), Box<dyn std::error::Error>> { | 98 | pub 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 | |||
212 | pub 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 | ||
259 | pub 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 | |||
273 | pub 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 | |||
207 | pub fn userlist_get_all_downloads(config: Cfg, list_id: String) -> Result<Vec<String>, Box<dyn std::error::Error>> { | 293 | pub 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 | ||
363 | pub fn s_insert_column(config: Cfg, table: String, column: String, c_type: String) -> Result<(), Box<dyn std::error::Error>> { | 449 | pub 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 @@ | |||
1 | use std::{fs::{File, read_dir}, io::Write, collections::HashMap}; | ||
2 | use futures_util::StreamExt; | ||
3 | use reqwest::Client; | ||
4 | |||
5 | use crate::List; | ||
6 | |||
7 | pub 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 | |||
27 | pub 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 | ||
15 | impl Input { | 16 | impl 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] |
128 | fn input_from() { | 136 | fn 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 | } |
@@ -4,13 +4,12 @@ pub mod commands; | |||
4 | pub mod input; | 4 | pub mod input; |
5 | pub mod db; | 5 | pub mod db; |
6 | pub mod error; | 6 | pub mod error; |
7 | pub mod files; | ||
7 | 8 | ||
8 | use std::{io::{Error, ErrorKind, Write}, fs::File}; | 9 | use std::io::{Error, ErrorKind}; |
9 | 10 | ||
10 | pub use apis::*; | 11 | pub use apis::*; |
11 | pub use commands::*; | 12 | pub use commands::*; |
12 | use futures_util::StreamExt; | ||
13 | use reqwest::Client; | ||
14 | 13 | ||
15 | #[derive(Debug, Clone, PartialEq, Eq)] | 14 | #[derive(Debug, Clone, PartialEq, Eq)] |
16 | pub enum Modloader { | 15 | pub enum Modloader { |
@@ -34,23 +33,3 @@ impl Modloader { | |||
34 | } | 33 | } |
35 | } | 34 | } |
36 | } | 35 | } |
37 | |||
38 | pub 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 | } | ||