diff options
-rw-r--r-- | config.ini | 1 | ||||
-rw-r--r-- | data.db | bin | 36864 -> 36864 bytes | |||
-rw-r--r-- | src/apis/modrinth.rs | 18 | ||||
-rw-r--r-- | src/commands/add.rs | 52 | ||||
-rw-r--r-- | src/commands/list.rs | 16 | ||||
-rw-r--r-- | src/commands/mod.rs | 6 | ||||
-rw-r--r-- | src/commands/modification.rs | 88 | ||||
-rw-r--r-- | src/commands/update.rs | 40 | ||||
-rw-r--r-- | src/config.rs | 3 | ||||
-rw-r--r-- | src/db.rs | 183 | ||||
-rw-r--r-- | src/input.rs | 12 | ||||
-rw-r--r-- | src/lib.rs | 22 | ||||
-rw-r--r-- | tests/db/mod.rs | 5 | ||||
-rw-r--r-- | tests/db_integration.rs | 7 |
14 files changed, 363 insertions, 90 deletions
@@ -1,4 +1,5 @@ | |||
1 | data = "./" | 1 | data = "./" |
2 | clean_remove = false | ||
2 | 3 | ||
3 | [apis] | 4 | [apis] |
4 | modrinth = "http://localhost:8080/" | 5 | modrinth = "http://localhost:8080/" |
Binary files differ | |||
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs index 3af5bbd..0c3eca5 100644 --- a/src/apis/modrinth.rs +++ b/src/apis/modrinth.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use serde::Deserialize; | 1 | use serde::Deserialize; |
2 | 2 | ||
3 | use crate::Modloader; | 3 | use crate::{Modloader, List}; |
4 | 4 | ||
5 | #[derive(Debug, Deserialize)] | 5 | #[derive(Debug, Deserialize)] |
6 | pub struct Project { | 6 | pub struct Project { |
@@ -68,7 +68,7 @@ pub enum Status { | |||
68 | unknown | 68 | unknown |
69 | } | 69 | } |
70 | 70 | ||
71 | #[derive(Debug, Deserialize)] | 71 | #[derive(Debug, Clone, Deserialize)] |
72 | pub struct Version { | 72 | pub struct Version { |
73 | pub name: String, | 73 | pub name: String, |
74 | pub version_number: String, | 74 | pub version_number: String, |
@@ -86,14 +86,14 @@ pub struct Version { | |||
86 | } | 86 | } |
87 | 87 | ||
88 | #[allow(non_camel_case_types)] | 88 | #[allow(non_camel_case_types)] |
89 | #[derive(Debug, Deserialize)] | 89 | #[derive(Debug, Clone, Deserialize)] |
90 | pub enum VersionType { | 90 | pub enum VersionType { |
91 | release, | 91 | release, |
92 | beta, | 92 | beta, |
93 | alpha | 93 | alpha |
94 | } | 94 | } |
95 | 95 | ||
96 | #[derive(Debug, Deserialize)] | 96 | #[derive(Debug, Clone, Deserialize)] |
97 | pub struct VersionFile { | 97 | pub struct VersionFile { |
98 | pub hashes: Hash, | 98 | pub hashes: Hash, |
99 | pub url: String, | 99 | pub url: String, |
@@ -102,7 +102,7 @@ pub struct VersionFile { | |||
102 | pub size: u32, | 102 | pub size: u32, |
103 | } | 103 | } |
104 | 104 | ||
105 | #[derive(Debug, Deserialize)] | 105 | #[derive(Debug, Clone, Deserialize)] |
106 | pub struct Hash { | 106 | pub struct Hash { |
107 | pub sha512: String, | 107 | pub sha512: String, |
108 | pub sha1: String, | 108 | pub sha1: String, |
@@ -130,7 +130,7 @@ pub async fn project(api: String, name: &str) -> Project { | |||
130 | serde_json::from_slice(&data.await.unwrap()).unwrap() | 130 | serde_json::from_slice(&data.await.unwrap()).unwrap() |
131 | } | 131 | } |
132 | 132 | ||
133 | pub async fn projects(api: String, ids: Vec<&str>) -> Vec<Project> { | 133 | pub async fn projects(api: String, ids: Vec<String>) -> Vec<Project> { |
134 | let all = ids.join(r#"",""#); | 134 | let all = ids.join(r#"",""#); |
135 | let url = format!(r#"projects?ids=["{}"]"#, all); | 135 | let url = format!(r#"projects?ids=["{}"]"#, all); |
136 | println!("{}", url); | 136 | println!("{}", url); |
@@ -140,14 +140,14 @@ pub async fn projects(api: String, ids: Vec<&str>) -> Vec<Project> { | |||
140 | serde_json::from_slice(&data.await.unwrap()).unwrap() | 140 | serde_json::from_slice(&data.await.unwrap()).unwrap() |
141 | } | 141 | } |
142 | 142 | ||
143 | pub async fn versions(api: String, id: String, loader: Modloader, mc_version: String) -> Vec<Version> { | 143 | pub async fn versions(api: String, id: String, list: List) -> Vec<Version> { |
144 | 144 | ||
145 | let loaderstr = match loader { | 145 | let loaderstr = match list.modloader { |
146 | Modloader::Forge => String::from("forge"), | 146 | Modloader::Forge => String::from("forge"), |
147 | Modloader::Fabric => String::from("fabric"), | 147 | Modloader::Fabric => String::from("fabric"), |
148 | }; | 148 | }; |
149 | 149 | ||
150 | let url = format!(r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, id, loaderstr, mc_version); | 150 | let url = format!(r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, id, loaderstr, list.mc_version); |
151 | 151 | ||
152 | let data = get(api, url); | 152 | let data = get(api, url); |
153 | 153 | ||
diff --git a/src/commands/add.rs b/src/commands/add.rs deleted file mode 100644 index ed4a6d8..0000000 --- a/src/commands/add.rs +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | use std::io::{Error, ErrorKind}; | ||
2 | |||
3 | use crate::{modrinth::{project, versions}, config::Cfg, db::insert_mod, Modloader, input::Input}; | ||
4 | |||
5 | pub async fn modification(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { | ||
6 | |||
7 | if args.is_none() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))) } | ||
8 | |||
9 | let arguments = Input::from(args.unwrap().join(" "))?; | ||
10 | |||
11 | if arguments.args.is_none() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; | ||
12 | |||
13 | match arguments.command.as_str() { | ||
14 | "add" => { | ||
15 | add(config, arguments.args.unwrap()).await | ||
16 | }, | ||
17 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_SUBCOMMAND"))) | ||
18 | } | ||
19 | } | ||
20 | |||
21 | pub async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { | ||
22 | |||
23 | if args.len() < 1 { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; | ||
24 | |||
25 | let project = project(String::from(&config.apis.modrinth), &args[0]).await; | ||
26 | |||
27 | dbg!(&project); | ||
28 | |||
29 | let loader = Modloader::Fabric; | ||
30 | |||
31 | if project.versions.is_empty() { panic!("This should never happen"); }; | ||
32 | |||
33 | let current_version = get_current(config, String::from(&project.id)).await?; | ||
34 | |||
35 | //add to current list and mod table | ||
36 | match insert_mod(project.id, project.title, current_version, project.versions, loader, String::from("1.19.2")) { | ||
37 | Err(err) => { Err(Box::new(err)) }, | ||
38 | Ok(()) => Ok(()), | ||
39 | } | ||
40 | |||
41 | } | ||
42 | |||
43 | async fn get_current(config: Cfg, id: String) -> Result<String, Box<dyn std::error::Error>> { | ||
44 | let available_versions = versions(config.apis.modrinth, id, Modloader::Fabric, String::from("1.19.2")).await; | ||
45 | |||
46 | match available_versions.len() { | ||
47 | 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))), | ||
48 | //TODO compare publish dates | ||
49 | 1.. => Ok(available_versions[0].id.to_string()), | ||
50 | _ => panic!("available_versions should never be negative"), | ||
51 | } | ||
52 | } | ||
diff --git a/src/commands/list.rs b/src/commands/list.rs index 6c260ce..3dfe1ad 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs | |||
@@ -1,12 +1,19 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::io::{Error, ErrorKind}; |
2 | 2 | ||
3 | use crate::{db::{insert_list, remove_list, change_list, get_lists, get_current_list}, Modloader, config::Cfg, input::Input}; | 3 | use crate::{db::{insert_list, remove_list, change_list, get_lists, get_current_list_id, get_list}, Modloader, config::Cfg, input::Input}; |
4 | |||
5 | #[derive(Clone)] | ||
6 | pub struct List { | ||
7 | pub id: String, | ||
8 | pub mc_version: String, | ||
9 | pub modloader: Modloader, | ||
10 | } | ||
4 | 11 | ||
5 | pub fn list(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { | 12 | pub fn list(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { |
6 | 13 | ||
7 | if args.is_none() { | 14 | if args.is_none() { |
8 | let lists = get_lists(config.clone())?; | 15 | let lists = get_lists(config.clone())?; |
9 | let current_list = get_current_list(config)?; | 16 | let current_list = get_current_list_id(config)?; |
10 | println!("Your lists:\n{}\n-----\nCurrently selected list: \"{}\"", lists.join(",\n"), current_list); | 17 | println!("Your lists:\n{}\n-----\nCurrently selected list: \"{}\"", lists.join(",\n"), current_list); |
11 | return Ok(()); | 18 | return Ok(()); |
12 | } | 19 | } |
@@ -29,6 +36,11 @@ pub fn list(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::e | |||
29 | } | 36 | } |
30 | } | 37 | } |
31 | 38 | ||
39 | pub fn get_current_list(config: Cfg) -> Result<List, Box<dyn std::error::Error>> { | ||
40 | let id = get_current_list_id(config.clone())?; | ||
41 | get_list(config, id) | ||
42 | } | ||
43 | |||
32 | fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { | 44 | fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { |
33 | match args.len() { | 45 | match args.len() { |
34 | 1 | 2 => Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))), | 46 | 1 | 2 => Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))), |
diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 6432746..5d008fd 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs | |||
@@ -1,5 +1,7 @@ | |||
1 | pub mod add; | 1 | pub mod modification; |
2 | pub mod list; | 2 | pub mod list; |
3 | pub mod update; | ||
3 | 4 | ||
4 | pub use add::*; | 5 | pub use modification::*; |
5 | pub use list::*; | 6 | pub use list::*; |
7 | pub use update::*; | ||
diff --git a/src/commands/modification.rs b/src/commands/modification.rs new file mode 100644 index 0000000..43e2180 --- /dev/null +++ b/src/commands/modification.rs | |||
@@ -0,0 +1,88 @@ | |||
1 | use std::io::{Error, ErrorKind}; | ||
2 | |||
3 | use crate::{modrinth::{project, versions, Version}, config::Cfg, db::{insert_mod, remove_mod_from_list, get_mod_id, insert_mod_in_list, get_mods, get_mods_from_list}, input::Input, get_current_list}; | ||
4 | |||
5 | pub async fn modification(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { | ||
6 | |||
7 | if args.is_none() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))) } | ||
8 | |||
9 | let arguments = Input::from(args.unwrap().join(" "))?; | ||
10 | |||
11 | if arguments.args.is_none() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; | ||
12 | |||
13 | match arguments.command.as_str() { | ||
14 | "add" => { | ||
15 | add(config, arguments.args.unwrap()).await | ||
16 | }, | ||
17 | "remove" => { | ||
18 | remove(config, arguments.args.unwrap()) | ||
19 | }, | ||
20 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_SUBCOMMAND"))) | ||
21 | } | ||
22 | } | ||
23 | |||
24 | async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { | ||
25 | |||
26 | if args.is_empty() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; | ||
27 | |||
28 | let current_list = get_current_list(config.clone())?; | ||
29 | |||
30 | let project = project(String::from(&config.apis.modrinth), &args[0]).await; | ||
31 | |||
32 | dbg!(&project); | ||
33 | |||
34 | if project.versions.is_empty() { panic!("This should never happen"); }; | ||
35 | |||
36 | let available_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), current_list.clone()).await; | ||
37 | |||
38 | let current_version = extract_current_version(available_versions.clone())?; | ||
39 | |||
40 | //add to current list and mod table | ||
41 | match get_mods_from_list(config.clone(), current_list.clone()) { | ||
42 | Ok(mods) => { | ||
43 | dbg!(&mods); | ||
44 | if mods.contains(&project.id) { | ||
45 | return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_ON_LIST"))); } | ||
46 | else { | ||
47 | insert_mod_in_list(config.clone(), current_list.clone(), String::from(&project.id), current_version, available_versions)?; | ||
48 | } | ||
49 | }, | ||
50 | Err(..) => insert_mod_in_list(config.clone(), current_list, String::from(&project.id), current_version, available_versions)?, | ||
51 | }; | ||
52 | |||
53 | match get_mods(config.clone()) { | ||
54 | Ok(mods) => { | ||
55 | dbg!(&mods); | ||
56 | if mods.contains(&project.id) { | ||
57 | return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_IN_DATABASE"))) | ||
58 | } else { | ||
59 | insert_mod(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?; | ||
60 | } | ||
61 | }, | ||
62 | Err(..) => insert_mod(config.clone(), String::from(&project.id), String::from(&project.title), project.versions)?, | ||
63 | }; | ||
64 | |||
65 | Ok(()) | ||
66 | } | ||
67 | |||
68 | fn remove(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> { | ||
69 | if args.is_empty() { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))); }; | ||
70 | |||
71 | let current_list = get_current_list(config.clone())?; | ||
72 | let mod_id = get_mod_id(config.clone(), String::from(&args[0]))?; | ||
73 | |||
74 | //TODO implement remove from modlist if not in any other lists && config clean is true | ||
75 | match remove_mod_from_list(config, current_list, mod_id) { | ||
76 | Err(err) => { Err(Box::new(err)) }, | ||
77 | Ok(()) => Ok(()), | ||
78 | } | ||
79 | } | ||
80 | |||
81 | fn extract_current_version(versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { | ||
82 | match versions.len() { | ||
83 | 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))), | ||
84 | //TODO compare publish dates | ||
85 | 1.. => Ok(versions[0].id.to_string()), | ||
86 | _ => panic!("available_versions should never be negative"), | ||
87 | } | ||
88 | } | ||
diff --git a/src/commands/update.rs b/src/commands/update.rs new file mode 100644 index 0000000..14c37ec --- /dev/null +++ b/src/commands/update.rs | |||
@@ -0,0 +1,40 @@ | |||
1 | use std::io::{Error, ErrorKind}; | ||
2 | |||
3 | use crate::{config::Cfg, modrinth::projects, get_current_list, db::{get_mods_from_list, get_versions}}; | ||
4 | |||
5 | pub async fn update(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { | ||
6 | |||
7 | let current_list = get_current_list(config.clone())?; | ||
8 | |||
9 | let mods = get_mods_from_list(config.clone(), current_list)?; | ||
10 | |||
11 | let mut projects = projects(String::from(&config.apis.modrinth), mods.clone()).await; | ||
12 | |||
13 | let mut versions = get_versions(config, mods)?; | ||
14 | |||
15 | projects.sort_by_key(|p| p.id.clone()); | ||
16 | |||
17 | versions.sort_by_key(|v| v.mod_id.clone()); | ||
18 | |||
19 | let mut update_stack: Vec<String> = vec![]; | ||
20 | |||
21 | for (index, project) in projects.iter().enumerate() { | ||
22 | |||
23 | let cmp_version = &versions[index]; | ||
24 | |||
25 | let p_id = &project.id; | ||
26 | let v_id = &cmp_version.mod_id; | ||
27 | |||
28 | if p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "COMPARE_SORTING_ERR"))); }; | ||
29 | println!("{}:{}", p_id, v_id); | ||
30 | |||
31 | if project.versions.join("|") != cmp_version.versions { | ||
32 | update_stack.push(String::from(&project.id)); | ||
33 | }; | ||
34 | }; | ||
35 | |||
36 | //TODO UPDATE | ||
37 | dbg!(update_stack); | ||
38 | |||
39 | Ok(()) | ||
40 | } | ||
diff --git a/src/config.rs b/src/config.rs index ba1b46a..58d399a 100644 --- a/src/config.rs +++ b/src/config.rs | |||
@@ -1,9 +1,10 @@ | |||
1 | use config::{Config, File, FileFormat}; | 1 | use config::{Config, File, FileFormat}; |
2 | use serde::Deserialize; | 2 | use serde::Deserialize; |
3 | 3 | ||
4 | #[derive(Debug, Clone,Deserialize)] | 4 | #[derive(Debug, Clone, Deserialize)] |
5 | pub struct Cfg { | 5 | pub struct Cfg { |
6 | pub data: String, | 6 | pub data: String, |
7 | pub clean_remove: bool, | ||
7 | pub apis: Apis, | 8 | pub apis: Apis, |
8 | } | 9 | } |
9 | 10 | ||
@@ -1,36 +1,164 @@ | |||
1 | use std::io::ErrorKind; | 1 | use std::io::ErrorKind; |
2 | 2 | ||
3 | use crate::{Modloader, config::Cfg}; | 3 | use crate::{Modloader, config::Cfg, List, modrinth::Version, get_modloader}; |
4 | 4 | ||
5 | //TODO use prepared statements | 5 | //TODO use prepared statements |
6 | 6 | ||
7 | pub fn insert_mod(id: String, name: String, current_version: String, old_versions: Vec<String>, mod_loader: Modloader, desired_mc_version: String) -> Result<(), sqlite::Error> { | 7 | //MODS |
8 | pub fn insert_mod(config: Cfg, id: String, name: String, versions: Vec<String>) -> Result<(), sqlite::Error> { | ||
8 | 9 | ||
9 | let connection = sqlite::open("./data.db").unwrap(); | 10 | println!("Inserting into modlist"); |
10 | 11 | ||
11 | let loader = match mod_loader { | 12 | let data = format!("{}/data.db", config.data); |
12 | Modloader::Fabric => "fabric", | 13 | let connection = sqlite::open(data).unwrap(); |
13 | Modloader::Forge => "forge", | 14 | |
14 | }; | 15 | let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}')", id, name, versions.join("|")); |
16 | |||
17 | connection.execute(sql) | ||
18 | } | ||
19 | |||
20 | pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: String, applicable_versions: Vec<Version>) -> Result<(), sqlite::Error> { | ||
21 | |||
22 | println!("Inserting into current list"); | ||
23 | |||
24 | let data = format!("{}/data.db", config.data); | ||
25 | let connection = sqlite::open(data).unwrap(); | ||
26 | |||
27 | let mut applicable_versions_vec = vec![]; | ||
28 | |||
29 | for ver in applicable_versions { | ||
30 | applicable_versions_vec.push(ver.id); | ||
31 | } | ||
32 | |||
33 | let sql = format!("INSERT INTO {} VALUES ('{}', '{}', '{}')", list.id, id, current_version, applicable_versions_vec.join("|")); | ||
34 | |||
35 | connection.execute(sql) | ||
36 | } | ||
37 | |||
38 | pub fn remove_mod_from_list(config: Cfg, list: List, mod_id: String) -> Result<(), sqlite::Error> { | ||
39 | let data = format!("{}/data.db", config.data); | ||
40 | let connection = sqlite::open(data).unwrap(); | ||
41 | |||
42 | let sql = format!("DELETE FROM {} WHERE mod_id = '{}'", list.id, mod_id); | ||
15 | 43 | ||
16 | let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}', '{}', '{}', '{}')", id, name, current_version, old_versions.join("|"), loader, desired_mc_version); | 44 | dbg!(&sql); |
17 | 45 | ||
18 | connection.execute(sql) | 46 | connection.execute(sql) |
19 | } | 47 | } |
20 | 48 | ||
21 | //LIST | 49 | pub fn get_mods(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::Error>> { |
22 | pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modloader) -> Result<(), sqlite::Error> { | 50 | |
23 | let data = format!("{}/data.db", config.data); | 51 | let data = format!("{}/data.db", config.data); |
24 | let connection = sqlite::open(data).unwrap(); | 52 | let connection = sqlite::open(data).unwrap(); |
53 | |||
54 | let sql = "SELECT id FROM mods"; | ||
25 | 55 | ||
26 | //Setup list in table | 56 | let mut mods: Vec<String> = Vec::new(); |
27 | let loader = match mod_loader { | 57 | //TODO catch sql errors better |
28 | Modloader::Fabric => "fabric", | 58 | connection.iterate(sql, |ids| { |
29 | Modloader::Forge => "forge", | 59 | if ids.is_empty() { return false; }; |
60 | for &(_column, value) in ids.iter() { | ||
61 | mods.push(String::from(value.unwrap())); | ||
62 | } | ||
63 | true | ||
64 | }).unwrap(); | ||
65 | match mods.is_empty() { | ||
66 | true => Err(Box::new(std::io::Error::new(ErrorKind::NotFound, "NO_MODS"))), | ||
67 | false => Ok(mods), | ||
68 | } | ||
69 | } | ||
70 | |||
71 | pub fn get_mods_from_list(config: Cfg, list: List) -> Result<Vec<String>, Box<dyn std::error::Error>> { | ||
72 | let data = format!("{}/data.db", config.data); | ||
73 | let connection = sqlite::open(data).unwrap(); | ||
74 | |||
75 | let sql = format!("SELECT mod_id FROM {}", list.id); | ||
76 | |||
77 | let mut mods: Vec<String> = Vec::new(); | ||
78 | //TODO catch sql errors better | ||
79 | connection.iterate(sql, |ids| { | ||
80 | if ids.is_empty() { return false; }; | ||
81 | for &(_column, value) in ids.iter() { | ||
82 | mods.push(String::from(value.unwrap())); | ||
83 | } | ||
84 | true | ||
85 | }).unwrap(); | ||
86 | match mods.is_empty() { | ||
87 | true => Err(Box::new(std::io::Error::new(ErrorKind::NotFound, "NO_MODS"))), | ||
88 | false => Ok(mods), | ||
89 | } | ||
90 | } | ||
91 | |||
92 | pub fn get_mod_id(config: Cfg, name: String) -> Result<String, Box<dyn std::error::Error>> { | ||
93 | let data = format!("{}/data.db", config.data); | ||
94 | let connection = sqlite::open(data).unwrap(); | ||
95 | |||
96 | let sql = format!("SELECT id FROM mods WHERE name = '{}'", name); | ||
97 | |||
98 | dbg!(&sql); | ||
99 | |||
100 | let mut modification = String::new(); | ||
101 | //TODO catch sql errors better | ||
102 | connection.iterate(sql, |id| { | ||
103 | if id.is_empty() { return false; }; | ||
104 | for &(_column, value) in id.iter() { | ||
105 | dbg!(&(_column, value)); | ||
106 | modification = String::from(value.unwrap()); | ||
107 | } | ||
108 | true | ||
109 | }).unwrap(); | ||
110 | |||
111 | dbg!(&modification); | ||
112 | |||
113 | if modification.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::NotFound, "MOD_NOT_IN_DATABASE"))) }; | ||
114 | |||
115 | Ok(modification) | ||
116 | } | ||
117 | |||
118 | #[derive(Debug, Clone)] | ||
119 | pub struct DBModlistVersions { | ||
120 | pub mod_id: String, | ||
121 | pub versions: String, | ||
122 | } | ||
123 | |||
124 | pub fn get_versions(config: Cfg, mods: Vec<String>) -> Result<Vec<DBModlistVersions>, Box<dyn std::error::Error>> { | ||
125 | let data = format!("{}/data.db", config.data); | ||
126 | let connection = sqlite::open(data).unwrap(); | ||
127 | |||
128 | let mut wherestr = String::from("WHERE"); | ||
129 | for (i, id) in mods.iter().enumerate() { | ||
130 | let mut or = " OR"; | ||
131 | if i == mods.len() - 1 { or = "" } | ||
132 | println!("Pushing {}({}) | OR: '{}'", id, i, or); | ||
133 | wherestr = format!("{} id = '{}'{}", wherestr, id, or); | ||
134 | } | ||
135 | |||
136 | let sql = format!("SELECT id, versions FROM mods {}", wherestr); | ||
137 | |||
138 | dbg!(&sql); | ||
139 | |||
140 | let mut versionmaps: Vec<DBModlistVersions> = Vec::new(); | ||
141 | //TODO catch sql errors better | ||
142 | let mut cursor = connection.prepare(sql).unwrap().into_cursor(); | ||
143 | |||
144 | while let Some(Ok(row)) = cursor.next() { | ||
145 | println!("{}: {}", row.get::<String, _>(0), row.get::<String, _>(1)); | ||
146 | versionmaps.push(DBModlistVersions { mod_id: row.get::<String, _>(0), versions: row.get::<String, _>(1) }) | ||
30 | }; | 147 | }; |
31 | 148 | ||
32 | let sql_list = format!("INSERT INTO lists VALUES ('{}', '{}', '{}')", id, mc_version, loader); | 149 | if versionmaps.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; |
33 | let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'mod_loader' TEXT )", id); | 150 | |
151 | Ok(versionmaps) | ||
152 | } | ||
153 | |||
154 | |||
155 | //LIST | ||
156 | pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modloader) -> Result<(), sqlite::Error> { | ||
157 | let data = format!("{}/data.db", config.data); | ||
158 | let connection = sqlite::open(data).unwrap(); | ||
159 | |||
160 | let sql_list = format!("INSERT INTO lists VALUES ('{}', '{}', '{}')", id, mc_version, mod_loader.stringify()); | ||
161 | let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB)", id); | ||
34 | let sql = format!("{};{};", sql_list, sql_table); | 162 | let sql = format!("{};{};", sql_list, sql_table); |
35 | 163 | ||
36 | connection.execute(sql) | 164 | connection.execute(sql) |
@@ -68,6 +196,27 @@ pub fn get_lists(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::Error>> | |||
68 | } | 196 | } |
69 | } | 197 | } |
70 | 198 | ||
199 | pub fn get_list(config: Cfg, id: String) -> Result<List, Box<dyn std::error::Error>> { | ||
200 | let data = format!("{}/data.db", config.data); | ||
201 | let connection = sqlite::open(data).unwrap(); | ||
202 | |||
203 | let sql = format!("SELECT mc_version, modloader FROM lists WHERE id = '{}'", id); | ||
204 | |||
205 | let mut list = vec![]; | ||
206 | //TODO catch sql errors better | ||
207 | connection.iterate(sql, |ids| { | ||
208 | if ids.is_empty() { return false; }; | ||
209 | for &(_column, value) in ids.iter() { | ||
210 | list.push(String::from(value.unwrap())); | ||
211 | } | ||
212 | true | ||
213 | }).unwrap(); | ||
214 | |||
215 | if list.len() != 2 { return Err(Box::new(std::io::Error::new(ErrorKind::InvalidData, "LIST_MISSING_DATA"))) }; | ||
216 | |||
217 | Ok(List { id, mc_version: String::from(&list[0]), modloader: get_modloader(String::from(&list[1]))? }) | ||
218 | } | ||
219 | |||
71 | //config | 220 | //config |
72 | pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { | 221 | pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { |
73 | let data = format!("{}/data.db", config.data); | 222 | let data = format!("{}/data.db", config.data); |
@@ -78,7 +227,7 @@ pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { | |||
78 | connection.execute(sql) | 227 | connection.execute(sql) |
79 | } | 228 | } |
80 | 229 | ||
81 | pub fn get_current_list(config: Cfg) -> Result<String, Box<dyn std::error::Error>> { | 230 | pub fn get_current_list_id(config: Cfg) -> Result<String, Box<dyn std::error::Error>> { |
82 | let data = format!("{}/data.db", config.data); | 231 | let data = format!("{}/data.db", config.data); |
83 | let connection = sqlite::open(data).unwrap(); | 232 | let connection = sqlite::open(data).unwrap(); |
84 | 233 | ||
diff --git a/src/input.rs b/src/input.rs index 061f1fd..0c13e67 100644 --- a/src/input.rs +++ b/src/input.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use std::io::{stdin, Error, ErrorKind}; | 1 | use std::io::{stdin, Error, ErrorKind}; |
2 | use crate::{add, config::Cfg, list}; | 2 | use crate::{config::Cfg, list, modification, update}; |
3 | 3 | ||
4 | pub struct Input { | 4 | pub struct Input { |
5 | pub command: String, | 5 | pub command: String, |
@@ -41,15 +41,15 @@ pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { | |||
41 | let input = Input::from(user_input.trim().to_string())?; | 41 | let input = Input::from(user_input.trim().to_string())?; |
42 | 42 | ||
43 | match input.command.as_str() { | 43 | match input.command.as_str() { |
44 | "add" => { | 44 | "mod" => { |
45 | if input.args == None { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_FEW_ARGUMENTS"))) }; | 45 | modification(config, input.args).await |
46 | if input.args.as_ref().unwrap().len() != 1 { return Err(Box::new(Error::new(ErrorKind::InvalidInput, "TOO_MANY_ARGUMENTS"))) }; | ||
47 | add(config, input.args.unwrap()[0].to_string()).await?; | ||
48 | Ok(()) | ||
49 | }, | 46 | }, |
50 | "list" => { | 47 | "list" => { |
51 | list(config, input.args) | 48 | list(config, input.args) |
52 | }, | 49 | }, |
50 | "update" => { | ||
51 | update(config).await | ||
52 | }, | ||
53 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), | 53 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), |
54 | } | 54 | } |
55 | } | 55 | } |
@@ -4,11 +4,31 @@ pub mod commands; | |||
4 | pub mod input; | 4 | pub mod input; |
5 | pub mod db; | 5 | pub mod db; |
6 | 6 | ||
7 | use std::io::{Error, ErrorKind}; | ||
8 | |||
7 | pub use apis::*; | 9 | pub use apis::*; |
8 | pub use commands::*; | 10 | pub use commands::*; |
9 | 11 | ||
10 | #[derive(Debug)] | 12 | #[derive(Debug, Clone)] |
11 | pub enum Modloader { | 13 | pub enum Modloader { |
12 | Fabric, | 14 | Fabric, |
13 | Forge | 15 | Forge |
14 | } | 16 | } |
17 | |||
18 | impl Modloader { | ||
19 | fn stringify(&self) -> String { | ||
20 | match self { | ||
21 | Modloader::Fabric => String::from("fabric"), | ||
22 | Modloader::Forge => String::from("forge"), | ||
23 | } | ||
24 | } | ||
25 | |||
26 | } | ||
27 | |||
28 | pub fn get_modloader(string: String) -> Result<Modloader, Box<dyn std::error::Error>> { | ||
29 | match string.as_str() { | ||
30 | "forge" => Ok(Modloader::Forge), | ||
31 | "fabric" => Ok(Modloader::Fabric), | ||
32 | _ => Err(Box::new(Error::new(ErrorKind::InvalidData, "UNKNOWN_MODLOADER"))) | ||
33 | } | ||
34 | } | ||
diff --git a/tests/db/mod.rs b/tests/db/mod.rs new file mode 100644 index 0000000..b5aed75 --- /dev/null +++ b/tests/db/mod.rs | |||
@@ -0,0 +1,5 @@ | |||
1 | use std::fs::File; | ||
2 | |||
3 | pub fn setup() { | ||
4 | File::create("./setuptests").unwrap(); | ||
5 | } | ||
diff --git a/tests/db_integration.rs b/tests/db_integration.rs new file mode 100644 index 0000000..82cfe0f --- /dev/null +++ b/tests/db_integration.rs | |||
@@ -0,0 +1,7 @@ | |||
1 | mod db; | ||
2 | |||
3 | #[test] | ||
4 | fn test_add() { | ||
5 | db::setup(); | ||
6 | assert!(true); | ||
7 | } | ||