summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.dbbin36864 -> 24576 bytes
-rw-r--r--src/apis/modrinth.rs10
-rw-r--r--src/commands/download.rs46
-rw-r--r--src/commands/mod.rs4
-rw-r--r--src/commands/modification.rs11
-rw-r--r--src/commands/setup.rs54
-rw-r--r--src/commands/update.rs2
-rw-r--r--src/db.rs171
-rw-r--r--src/input.rs22
9 files changed, 281 insertions, 39 deletions
diff --git a/data.db b/data.db
index 2d258dc..f878ca1 100644
--- a/data.db
+++ b/data.db
Binary files differ
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs
index c71b47f..abb8eec 100644
--- a/src/apis/modrinth.rs
+++ b/src/apis/modrinth.rs
@@ -156,6 +156,16 @@ pub async fn versions(api: String, id: String, list: List) -> Vec<Version> {
156 serde_json::from_slice(&data.await.unwrap()).unwrap() 156 serde_json::from_slice(&data.await.unwrap()).unwrap()
157} 157}
158 158
159pub async fn get_raw_versions(api: String, versions: Vec<String>) -> Vec<Version> {
160 println!("Getting versions");
161
162 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
163
164 let data = get(api, url).await;
165
166 serde_json::from_slice(&data.unwrap()).unwrap()
167}
168
159pub fn extract_current_version(versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { 169pub fn extract_current_version(versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> {
160 match versions.len() { 170 match versions.len() {
161 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))), 171 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))),
diff --git a/src/commands/download.rs b/src/commands/download.rs
new file mode 100644
index 0000000..05c54cb
--- /dev/null
+++ b/src/commands/download.rs
@@ -0,0 +1,46 @@
1use std::{io::Write, fs::File};
2
3use reqwest::Client;
4
5use futures_util::StreamExt;
6
7use crate::{get_current_list, config::Cfg, db::get_dl_links};
8
9pub async fn download(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
10 let list = get_current_list(config.clone())?;
11
12 let links = get_dl_links(config.clone(), list)?;
13
14 download_links(config, links).await?;
15
16 Ok(())
17}
18
19async fn download_links(config: Cfg, links: Vec<String>) -> Result<String, Box<dyn std::error::Error>> {
20
21 let dl_path = String::from(&config.downloads);
22
23 for link in links {
24 let filename = link.split('/').last().unwrap();
25 let dl_path_file = format!("{}/{}", config.downloads, filename);
26 println!("Downloading {}", link);
27
28 let res = Client::new()
29 .get(String::from(&link))
30 .send()
31 .await
32 .or(Err(format!("Failed to GET from '{}'", &link)))?;
33
34 // download chunks
35 let mut file = File::create(String::from(&dl_path_file)).or(Err(format!("Failed to create file '{}'", dl_path_file)))?;
36 let mut stream = res.bytes_stream();
37
38 while let Some(item) = stream.next().await {
39 let chunk = item.or(Err("Error while downloading file"))?;
40 file.write_all(&chunk)
41 .or(Err("Error while writing to file"))?;
42 }
43 }
44
45 Ok(dl_path)
46}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 5d008fd..20badcb 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,7 +1,11 @@
1pub mod modification; 1pub mod modification;
2pub mod list; 2pub mod list;
3pub mod update; 3pub mod update;
4pub mod setup;
5pub mod download;
4 6
5pub use modification::*; 7pub use modification::*;
6pub use list::*; 8pub use list::*;
7pub use update::*; 9pub use update::*;
10pub use setup::*;
11pub use download::*;
diff --git a/src/commands/modification.rs b/src/commands/modification.rs
index b90c82c..e877a63 100644
--- a/src/commands/modification.rs
+++ b/src/commands/modification.rs
@@ -34,9 +34,12 @@ async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::E
34 if project.versions.is_empty() { panic!("This should never happen"); }; 34 if project.versions.is_empty() { panic!("This should never happen"); };
35 35
36 let available_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), current_list.clone()).await; 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 37
38 let current_id = extract_current_version(available_versions.clone())?;
39
40 let current_version = available_versions.clone().into_iter().find(|v| v.id == current_id).unwrap();
41
42 let file = current_version.files.into_iter().find(|f| f.primary).unwrap().url;
40 //add to current list and mod table 43 //add to current list and mod table
41 match get_mods_from_list(config.clone(), current_list.clone()) { 44 match get_mods_from_list(config.clone(), current_list.clone()) {
42 Ok(mods) => { 45 Ok(mods) => {
@@ -44,10 +47,10 @@ async fn add(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::E
44 if mods.contains(&project.id) { 47 if mods.contains(&project.id) {
45 return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_ON_LIST"))); } 48 return Err(Box::new(Error::new(ErrorKind::Other, "MOD_ALREADY_ON_LIST"))); }
46 else { 49 else {
47 insert_mod_in_list(config.clone(), current_list.clone(), String::from(&project.id), current_version, available_versions)?; 50 insert_mod_in_list(config.clone(), current_list.clone(), String::from(&project.id), current_version.id, available_versions, file)?;
48 } 51 }
49 }, 52 },
50 Err(..) => insert_mod_in_list(config.clone(), current_list, String::from(&project.id), current_version, available_versions)?, 53 Err(..) => insert_mod_in_list(config.clone(), current_list, String::from(&project.id), current_version.id, available_versions, file)?,
51 }; 54 };
52 55
53 match get_mods(config.clone()) { 56 match get_mods(config.clone()) {
diff --git a/src/commands/setup.rs b/src/commands/setup.rs
new file mode 100644
index 0000000..0223a21
--- /dev/null
+++ b/src/commands/setup.rs
@@ -0,0 +1,54 @@
1use std::{fs::File, path::Path, io::{Error, ErrorKind}};
2
3use crate::{config::Cfg, db::{db_setup, get_dbversion, create_dbversion, insert_column, get_lists, get_list, get_current_versions, insert_dl_link}, modrinth::get_raw_versions};
4
5pub async fn setup(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
6
7 let db_file = format!("{}/data.db", String::from(&config.data));
8
9 if !Path::new(&db_file).exists() {
10 return create(config, db_file);
11 }
12
13 match get_dbversion(config.clone()) {
14 Ok(ver) => {
15 match ver.as_str() {
16 _ => return Err(Box::new(Error::new(ErrorKind::Other, "UNKNOWN_VERSION")))
17 }
18 },
19 Err(..) => to_02(config).await?
20 };
21
22 Ok(())
23}
24
25fn create(config: Cfg, db_file: String) -> Result<(), Box<dyn std::error::Error>> {
26 File::create(db_file)?;
27 db_setup(config)?;
28 Ok(())
29}
30
31async fn to_02(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
32 let lists = get_lists(config.clone())?;
33
34 for list in lists {
35 println!("Updating {}", list);
36 insert_column(config.clone(), String::from(&list), String::from("current_download"), sqlite::Type::String)?;
37
38 let full_list = get_list(config.clone(), String::from(&list))?;
39
40 let versions = get_current_versions(config.clone(), full_list.clone())?;
41
42 let raw_versions = get_raw_versions(String::from(&config.apis.modrinth), versions).await;
43
44 for ver in raw_versions {
45 println!("Adding link for {}", ver.project_id);
46 let file = ver.files.into_iter().find(|f| f.primary).unwrap();
47 insert_dl_link(config.clone(), full_list.clone(), ver.project_id, file.url)?;
48 }
49 };
50 create_dbversion(config)?;
51
52
53 Ok(())
54}
diff --git a/src/commands/update.rs b/src/commands/update.rs
index 6275bce..284d289 100644
--- a/src/commands/update.rs
+++ b/src/commands/update.rs
@@ -37,7 +37,7 @@ pub async fn update(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
37 }; 37 };
38 //println!("{:?}", updatestack); 38 //println!("{:?}", updatestack);
39 39
40 //download_updates(config, updatestack).await?; 40 download_updates(config, updatestack).await?;
41 41
42 Ok(()) 42 Ok(())
43} 43}
diff --git a/src/db.rs b/src/db.rs
index 497cc15..88a104d 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -12,12 +12,14 @@ pub fn insert_mod(config: Cfg, id: String, name: String, versions: Vec<String>)
12 let data = format!("{}/data.db", config.data); 12 let data = format!("{}/data.db", config.data);
13 let connection = sqlite::open(data).unwrap(); 13 let connection = sqlite::open(data).unwrap();
14 14
15 let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}')", id, name, versions.join("|")); 15 let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}')", id, name.replace('\'', ""), versions.join("|"));
16
17 dbg!(&sql);
16 18
17 connection.execute(sql) 19 connection.execute(sql)
18} 20}
19 21
20pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: String, applicable_versions: Vec<Version>) -> Result<(), sqlite::Error> { 22pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: String, applicable_versions: Vec<Version>, current_link: String) -> Result<(), sqlite::Error> {
21 23
22 println!("Inserting into current list"); 24 println!("Inserting into current list");
23 25
@@ -30,8 +32,8 @@ pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version:
30 applicable_versions_vec.push(ver.id); 32 applicable_versions_vec.push(ver.id);
31 } 33 }
32 34
33 let sql = format!("INSERT INTO {} VALUES ('{}', '{}', '{}')", list.id, id, current_version, applicable_versions_vec.join("|")); 35 let sql = format!("INSERT INTO {} VALUES ('{}', '{}', '{}', '{}')", list.id, id, current_version, applicable_versions_vec.join("|"), current_link);
34 36
35 connection.execute(sql) 37 connection.execute(sql)
36} 38}
37 39
@@ -122,33 +124,33 @@ pub struct DBModlistVersions {
122} 124}
123 125
124pub fn get_versions(config: Cfg, mods: Vec<String>) -> Result<Vec<DBModlistVersions>, Box<dyn std::error::Error>> { 126pub fn get_versions(config: Cfg, mods: Vec<String>) -> Result<Vec<DBModlistVersions>, Box<dyn std::error::Error>> {
125 let data = format!("{}/data.db", config.data); 127let data = format!("{}/data.db", config.data);
126 let connection = sqlite::open(data).unwrap(); 128let connection = sqlite::open(data).unwrap();
127 129
128 let mut wherestr = String::from("WHERE"); 130let mut wherestr = String::from("WHERE");
129 for (i, id) in mods.iter().enumerate() { 131for (i, id) in mods.iter().enumerate() {
130 let mut or = " OR"; 132 let mut or = " OR";
131 if i == mods.len() - 1 { or = "" } 133 if i == mods.len() - 1 { or = "" }
132 println!("Pushing {}({}) | OR: '{}'", id, i, or); 134 println!("Pushing {}({}) | OR: '{}'", id, i, or);
133 wherestr = format!("{} id = '{}'{}", wherestr, id, or); 135 wherestr = format!("{} id = '{}'{}", wherestr, id, or);
134 } 136}
135 137
136 let sql = format!("SELECT id, versions FROM mods {}", wherestr); 138let sql = format!("SELECT id, versions FROM mods {}", wherestr);
137
138 dbg!(&sql);
139 139
140 let mut versionmaps: Vec<DBModlistVersions> = Vec::new(); 140dbg!(&sql);
141 //TODO catch sql errors better
142 let mut cursor = connection.prepare(sql).unwrap().into_cursor();
143 141
144 while let Some(Ok(row)) = cursor.next() { 142let mut versionmaps: Vec<DBModlistVersions> = Vec::new();
145 println!("{}: {}", row.get::<String, _>(0), row.get::<String, _>(1)); 143//TODO catch sql errors better
146 versionmaps.push(DBModlistVersions { mod_id: row.get::<String, _>(0), versions: row.get::<String, _>(1) }) 144let mut cursor = connection.prepare(sql).unwrap().into_cursor();
147 };
148 145
149 if versionmaps.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; 146while let Some(Ok(row)) = cursor.next() {
147 println!("{}: {}", row.get::<String, _>(0), row.get::<String, _>(1));
148 versionmaps.push(DBModlistVersions { mod_id: row.get::<String, _>(0), versions: row.get::<String, _>(1) })
149};
150 150
151 Ok(versionmaps) 151if versionmaps.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); };
152
153Ok(versionmaps)
152} 154}
153 155
154pub fn get_list_version(config: Cfg, list: List, mod_id: String) -> Result<String, Box<dyn std::error::Error>> { 156pub fn get_list_version(config: Cfg, list: List, mod_id: String) -> Result<String, Box<dyn std::error::Error>> {
@@ -179,7 +181,7 @@ pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modl
179 let connection = sqlite::open(data).unwrap(); 181 let connection = sqlite::open(data).unwrap();
180 182
181 let sql_list = format!("INSERT INTO lists VALUES ('{}', '{}', '{}')", id, mc_version, mod_loader.stringify()); 183 let sql_list = format!("INSERT INTO lists VALUES ('{}', '{}', '{}')", id, mc_version, mod_loader.stringify());
182 let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB)", id); 184 let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT)", id);
183 let sql = format!("{};{};", sql_list, sql_table); 185 let sql = format!("{};{};", sql_list, sql_table);
184 186
185 connection.execute(sql) 187 connection.execute(sql)
@@ -217,6 +219,27 @@ pub fn get_lists(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::Error>>
217 } 219 }
218} 220}
219 221
222pub fn get_current_versions(config: Cfg, list: List) -> Result<Vec<String>, Box<std::io::Error>> {
223 let data = format!("{}/data.db", config.data);
224 let connection = sqlite::open(data).unwrap();
225
226 let sql = format!("SELECT current_version FROM {}", list.id);
227
228 dbg!(&sql);
229
230 let mut versions: Vec<String> = Vec::new();
231 //TODO catch sql errors better
232 let mut cursor = connection.prepare(sql).unwrap().into_cursor();
233
234 while let Some(Ok(row)) = cursor.next() {
235 versions.push(row.get::<String, _>(0));
236 };
237
238 if versions.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); };
239
240 Ok(versions)
241}
242
220pub fn get_list(config: Cfg, id: String) -> Result<List, Box<dyn std::error::Error>> { 243pub fn get_list(config: Cfg, id: String) -> Result<List, Box<dyn std::error::Error>> {
221 let data = format!("{}/data.db", config.data); 244 let data = format!("{}/data.db", config.data);
222 let connection = sqlite::open(data).unwrap(); 245 let connection = sqlite::open(data).unwrap();
@@ -247,6 +270,38 @@ pub fn change_list_versions(config: Cfg, list: List, current_version: String, ve
247 connection.execute(sql) 270 connection.execute(sql)
248} 271}
249 272
273//DOWNLOAD
274
275pub fn insert_dl_link(config: Cfg, list: List, mod_id: String, link: String) -> Result<(), sqlite::Error> {
276 let data = format!("{}/data.db", config.data);
277 let connection = sqlite::open(data).unwrap();
278
279 let sql = format!("UPDATE {} SET current_download = '{}' WHERE mod_id = '{}'", list.id, link, mod_id);
280
281 connection.execute(sql)
282}
283
284pub fn get_dl_links(config: Cfg, list: List) -> Result<Vec<String>, Box<std::io::Error>> {
285 let data = format!("{}/data.db", config.data);
286 let connection = sqlite::open(data).unwrap();
287
288 let sql = format!("SELECT current_download FROM {}", list.id);
289
290 dbg!(&sql);
291
292 let mut links: Vec<String> = Vec::new();
293 //TODO catch sql errors better
294 let mut cursor = connection.prepare(sql).unwrap().into_cursor();
295
296 while let Some(Ok(row)) = cursor.next() {
297 links.push(row.get::<String, _>(0));
298 };
299
300 if links.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); };
301
302 Ok(links)
303}
304
250//config 305//config
251pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { 306pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> {
252 let data = format!("{}/data.db", config.data); 307 let data = format!("{}/data.db", config.data);
@@ -278,3 +333,65 @@ pub fn get_current_list_id(config: Cfg) -> Result<String, Box<dyn std::error::Er
278 }; 333 };
279 Ok(list) 334 Ok(list)
280} 335}
336
337pub fn update_dbversion(config: Cfg, ver: String) -> Result<(), sqlite::Error> {
338 let data = format!("{}/data.db", config.data);
339 let connection = sqlite::open(data).unwrap();
340
341 let sql = format!("UPDATE user_config SET value = '{}' WHERE id = 'db_version'", ver);
342
343 connection.execute(sql)
344}
345
346pub fn create_dbversion(config: Cfg) -> Result<(), sqlite::Error> {
347 let data = format!("{}/data.db", config.data);
348 let connection = sqlite::open(data).unwrap();
349 let sql = "INSERT INTO 'user_config' VALUES ( 'db_version', '0.2' );";
350 connection.execute(sql)
351}
352
353pub fn get_dbversion(config: Cfg) -> Result<String, Box<dyn std::error::Error>> {
354 let data = format!("{}/data.db", config.data);
355 let connection = sqlite::open(data).unwrap();
356
357 let sql = "SELECT db_version FROM user_config";
358
359 let mut ver: String = String::new();
360 //TODO catch sql errors better
361 connection.iterate(sql, |ids| {
362 if ids.is_empty() { return false; };
363 for &(_column, value) in ids.iter() {
364 ver = String::from(value.unwrap());
365 }
366 true
367 })?;
368 if ver.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_DBVERSION"))); };
369 Ok(ver)
370}
371
372pub fn db_setup(config: Cfg) -> Result<(), sqlite::Error> {
373 println!("Initiating database");
374
375 let data = format!("{}/data.db", config.data);
376 let connection = sqlite::open(data).unwrap();
377
378 let sql = "CREATE TABLE 'user_config' ( 'id' TEXT, 'value' TEXT ); CREATE TABLE 'mods' ( 'id' TEXT, 'name' TEXT, 'versions' TEXT ); CREATE TABLE 'lists' ( 'id' TEXT, 'mc_version' TEXT, 'modloader' TEXT ); INSERT INTO 'user_config' VALUES ( 'db_version', '0.2' ); INSERT INTO 'user_config' VALUES ( 'current_list', '...' )";
379
380 connection.execute(sql)
381}
382
383pub fn insert_column(config: Cfg, table: String, column: String, c_type: sqlite::Type) -> Result<(), sqlite::Error> {
384 let data = format!("{}/data.db", config.data);
385 let connection = sqlite::open(data).unwrap();
386
387 let ct = match c_type {
388 sqlite::Type::Null => "NULL",
389 sqlite::Type::Float => "FLOAT",
390 sqlite::Type::Binary => "BINARY",
391 sqlite::Type::String => "TEXT",
392 sqlite::Type::Integer => "INT",
393 };
394
395 let sql = format!("ALTER TABLE {} ADD '{}' {}", table, column, ct);
396 connection.execute(sql)
397}
diff --git a/src/input.rs b/src/input.rs
index e0c9ae9..c7e82d9 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,5 +1,5 @@
1use std::io::{stdin, Error, ErrorKind}; 1use std::{io::{Error, ErrorKind}, env};
2use crate::{config::Cfg, list, modification, update}; 2use crate::{config::Cfg, list, modification, update, setup, download};
3 3
4#[derive(Debug, PartialEq, Eq)] 4#[derive(Debug, PartialEq, Eq)]
5pub struct Input { 5pub struct Input {
@@ -32,12 +32,14 @@ impl Input {
32} 32}
33 33
34pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { 34pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
35 let mut user_input = String::new(); 35 let mut args: Vec<String> = env::args().collect();
36 stdin() 36 dbg!(&args);
37 .read_line(&mut user_input) 37 args.reverse();
38 .expect("ERROR"); 38 args.pop();
39 args.reverse();
40 dbg!(&args);
39 41
40 let input = Input::from(user_input.trim().to_string())?; 42 let input = Input::from(args.join(" "))?;
41 43
42 match input.command.as_str() { 44 match input.command.as_str() {
43 "mod" => { 45 "mod" => {
@@ -49,6 +51,12 @@ pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> {
49 "update" => { 51 "update" => {
50 update(config).await 52 update(config).await
51 }, 53 },
54 "setup" => {
55 setup(config).await
56 },
57 "download" => {
58 download(config).await
59 },
52 _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), 60 _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))),
53 } 61 }
54} 62}