From 0f5223d3d3f6aeb6bb1a0b09ad3d4ef5731774dd Mon Sep 17 00:00:00 2001 From: fxqnlr Date: Sat, 5 Nov 2022 21:53:24 +0100 Subject: added setup & download; direct input --- src/apis/modrinth.rs | 10 +++ src/commands/download.rs | 46 ++++++++++++ src/commands/mod.rs | 4 + src/commands/modification.rs | 11 ++- src/commands/setup.rs | 54 ++++++++++++++ src/commands/update.rs | 2 +- src/db.rs | 171 ++++++++++++++++++++++++++++++++++++------- src/input.rs | 22 ++++-- 8 files changed, 281 insertions(+), 39 deletions(-) create mode 100644 src/commands/download.rs create mode 100644 src/commands/setup.rs (limited to 'src') 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 { serde_json::from_slice(&data.await.unwrap()).unwrap() } +pub async fn get_raw_versions(api: String, versions: Vec) -> Vec { + println!("Getting versions"); + + let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); + + let data = get(api, url).await; + + serde_json::from_slice(&data.unwrap()).unwrap() +} + pub fn extract_current_version(versions: Vec) -> Result> { match versions.len() { 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 @@ +use std::{io::Write, fs::File}; + +use reqwest::Client; + +use futures_util::StreamExt; + +use crate::{get_current_list, config::Cfg, db::get_dl_links}; + +pub async fn download(config: Cfg) -> Result<(), Box> { + let list = get_current_list(config.clone())?; + + let links = get_dl_links(config.clone(), list)?; + + download_links(config, links).await?; + + Ok(()) +} + +async fn download_links(config: Cfg, links: Vec) -> Result> { + + let dl_path = String::from(&config.downloads); + + for link in links { + let filename = link.split('/').last().unwrap(); + let dl_path_file = format!("{}/{}", config.downloads, filename); + println!("Downloading {}", link); + + let res = Client::new() + .get(String::from(&link)) + .send() + .await + .or(Err(format!("Failed to GET from '{}'", &link)))?; + + // download chunks + let mut file = File::create(String::from(&dl_path_file)).or(Err(format!("Failed to create file '{}'", dl_path_file)))?; + let mut stream = res.bytes_stream(); + + while let Some(item) = stream.next().await { + let chunk = item.or(Err("Error while downloading file"))?; + file.write_all(&chunk) + .or(Err("Error while writing to file"))?; + } + } + + Ok(dl_path) +} 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 @@ pub mod modification; pub mod list; pub mod update; +pub mod setup; +pub mod download; pub use modification::*; pub use list::*; pub use update::*; +pub use setup::*; +pub 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) -> Result<(), Box { @@ -44,10 +47,10 @@ async fn add(config: Cfg, args: Vec) -> Result<(), Box insert_mod_in_list(config.clone(), current_list, String::from(&project.id), current_version, available_versions)?, + Err(..) => insert_mod_in_list(config.clone(), current_list, String::from(&project.id), current_version.id, available_versions, file)?, }; 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 @@ +use std::{fs::File, path::Path, io::{Error, ErrorKind}}; + +use 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}; + +pub async fn setup(config: Cfg) -> Result<(), Box> { + + let db_file = format!("{}/data.db", String::from(&config.data)); + + if !Path::new(&db_file).exists() { + return create(config, db_file); + } + + match get_dbversion(config.clone()) { + Ok(ver) => { + match ver.as_str() { + _ => return Err(Box::new(Error::new(ErrorKind::Other, "UNKNOWN_VERSION"))) + } + }, + Err(..) => to_02(config).await? + }; + + Ok(()) +} + +fn create(config: Cfg, db_file: String) -> Result<(), Box> { + File::create(db_file)?; + db_setup(config)?; + Ok(()) +} + +async fn to_02(config: Cfg) -> Result<(), Box> { + let lists = get_lists(config.clone())?; + + for list in lists { + println!("Updating {}", list); + insert_column(config.clone(), String::from(&list), String::from("current_download"), sqlite::Type::String)?; + + let full_list = get_list(config.clone(), String::from(&list))?; + + let versions = get_current_versions(config.clone(), full_list.clone())?; + + let raw_versions = get_raw_versions(String::from(&config.apis.modrinth), versions).await; + + for ver in raw_versions { + println!("Adding link for {}", ver.project_id); + let file = ver.files.into_iter().find(|f| f.primary).unwrap(); + insert_dl_link(config.clone(), full_list.clone(), ver.project_id, file.url)?; + } + }; + create_dbversion(config)?; + + + Ok(()) +} 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> { }; //println!("{:?}", updatestack); - //download_updates(config, updatestack).await?; + download_updates(config, updatestack).await?; Ok(()) } 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) let data = format!("{}/data.db", config.data); let connection = sqlite::open(data).unwrap(); - let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}')", id, name, versions.join("|")); + let sql = format!("INSERT INTO mods VALUES ('{}', '{}', '{}')", id, name.replace('\'', ""), versions.join("|")); + + dbg!(&sql); connection.execute(sql) } -pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: String, applicable_versions: Vec) -> Result<(), sqlite::Error> { +pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: String, applicable_versions: Vec, current_link: String) -> Result<(), sqlite::Error> { println!("Inserting into current list"); @@ -30,8 +32,8 @@ pub fn insert_mod_in_list(config: Cfg, list: List, id: String, current_version: applicable_versions_vec.push(ver.id); } - let sql = format!("INSERT INTO {} VALUES ('{}', '{}', '{}')", list.id, id, current_version, applicable_versions_vec.join("|")); - + let sql = format!("INSERT INTO {} VALUES ('{}', '{}', '{}', '{}')", list.id, id, current_version, applicable_versions_vec.join("|"), current_link); + connection.execute(sql) } @@ -122,33 +124,33 @@ pub struct DBModlistVersions { } pub fn get_versions(config: Cfg, mods: Vec) -> Result, Box> { - let data = format!("{}/data.db", config.data); - let connection = sqlite::open(data).unwrap(); - - let mut wherestr = String::from("WHERE"); - for (i, id) in mods.iter().enumerate() { - let mut or = " OR"; - if i == mods.len() - 1 { or = "" } - println!("Pushing {}({}) | OR: '{}'", id, i, or); - wherestr = format!("{} id = '{}'{}", wherestr, id, or); - } +let data = format!("{}/data.db", config.data); +let connection = sqlite::open(data).unwrap(); + +let mut wherestr = String::from("WHERE"); +for (i, id) in mods.iter().enumerate() { + let mut or = " OR"; + if i == mods.len() - 1 { or = "" } + println!("Pushing {}({}) | OR: '{}'", id, i, or); + wherestr = format!("{} id = '{}'{}", wherestr, id, or); +} - let sql = format!("SELECT id, versions FROM mods {}", wherestr); - - dbg!(&sql); +let sql = format!("SELECT id, versions FROM mods {}", wherestr); - let mut versionmaps: Vec = Vec::new(); - //TODO catch sql errors better - let mut cursor = connection.prepare(sql).unwrap().into_cursor(); +dbg!(&sql); - while let Some(Ok(row)) = cursor.next() { - println!("{}: {}", row.get::(0), row.get::(1)); - versionmaps.push(DBModlistVersions { mod_id: row.get::(0), versions: row.get::(1) }) - }; +let mut versionmaps: Vec = Vec::new(); +//TODO catch sql errors better +let mut cursor = connection.prepare(sql).unwrap().into_cursor(); - if versionmaps.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; +while let Some(Ok(row)) = cursor.next() { + println!("{}: {}", row.get::(0), row.get::(1)); + versionmaps.push(DBModlistVersions { mod_id: row.get::(0), versions: row.get::(1) }) +}; - Ok(versionmaps) +if versionmaps.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; + +Ok(versionmaps) } pub fn get_list_version(config: Cfg, list: List, mod_id: String) -> Result> { @@ -179,7 +181,7 @@ pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modl let connection = sqlite::open(data).unwrap(); let sql_list = format!("INSERT INTO lists VALUES ('{}', '{}', '{}')", id, mc_version, mod_loader.stringify()); - let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB)", id); + let sql_table = format!("CREATE TABLE '{}' ( 'mod_id' TEXT, 'current_version' TEXT, 'applicable_versions' BLOB, 'current_download' TEXT)", id); let sql = format!("{};{};", sql_list, sql_table); connection.execute(sql) @@ -217,6 +219,27 @@ pub fn get_lists(config: Cfg) -> Result, Box> } } +pub fn get_current_versions(config: Cfg, list: List) -> Result, Box> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let sql = format!("SELECT current_version FROM {}", list.id); + + dbg!(&sql); + + let mut versions: Vec = Vec::new(); + //TODO catch sql errors better + let mut cursor = connection.prepare(sql).unwrap().into_cursor(); + + while let Some(Ok(row)) = cursor.next() { + versions.push(row.get::(0)); + }; + + if versions.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; + + Ok(versions) +} + pub fn get_list(config: Cfg, id: String) -> Result> { let data = format!("{}/data.db", config.data); let connection = sqlite::open(data).unwrap(); @@ -247,6 +270,38 @@ pub fn change_list_versions(config: Cfg, list: List, current_version: String, ve connection.execute(sql) } +//DOWNLOAD + +pub fn insert_dl_link(config: Cfg, list: List, mod_id: String, link: String) -> Result<(), sqlite::Error> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let sql = format!("UPDATE {} SET current_download = '{}' WHERE mod_id = '{}'", list.id, link, mod_id); + + connection.execute(sql) +} + +pub fn get_dl_links(config: Cfg, list: List) -> Result, Box> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let sql = format!("SELECT current_download FROM {}", list.id); + + dbg!(&sql); + + let mut links: Vec = Vec::new(); + //TODO catch sql errors better + let mut cursor = connection.prepare(sql).unwrap().into_cursor(); + + while let Some(Ok(row)) = cursor.next() { + links.push(row.get::(0)); + }; + + if links.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; + + Ok(links) +} + //config pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { let data = format!("{}/data.db", config.data); @@ -278,3 +333,65 @@ pub fn get_current_list_id(config: Cfg) -> Result Result<(), sqlite::Error> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let sql = format!("UPDATE user_config SET value = '{}' WHERE id = 'db_version'", ver); + + connection.execute(sql) +} + +pub fn create_dbversion(config: Cfg) -> Result<(), sqlite::Error> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + let sql = "INSERT INTO 'user_config' VALUES ( 'db_version', '0.2' );"; + connection.execute(sql) +} + +pub fn get_dbversion(config: Cfg) -> Result> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let sql = "SELECT db_version FROM user_config"; + + let mut ver: String = String::new(); + //TODO catch sql errors better + connection.iterate(sql, |ids| { + if ids.is_empty() { return false; }; + for &(_column, value) in ids.iter() { + ver = String::from(value.unwrap()); + } + true + })?; + if ver.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_DBVERSION"))); }; + Ok(ver) +} + +pub fn db_setup(config: Cfg) -> Result<(), sqlite::Error> { + println!("Initiating database"); + + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + 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', '...' )"; + + connection.execute(sql) +} + +pub fn insert_column(config: Cfg, table: String, column: String, c_type: sqlite::Type) -> Result<(), sqlite::Error> { + let data = format!("{}/data.db", config.data); + let connection = sqlite::open(data).unwrap(); + + let ct = match c_type { + sqlite::Type::Null => "NULL", + sqlite::Type::Float => "FLOAT", + sqlite::Type::Binary => "BINARY", + sqlite::Type::String => "TEXT", + sqlite::Type::Integer => "INT", + }; + + let sql = format!("ALTER TABLE {} ADD '{}' {}", table, column, ct); + connection.execute(sql) +} 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 @@ -use std::io::{stdin, Error, ErrorKind}; -use crate::{config::Cfg, list, modification, update}; +use std::{io::{Error, ErrorKind}, env}; +use crate::{config::Cfg, list, modification, update, setup, download}; #[derive(Debug, PartialEq, Eq)] pub struct Input { @@ -32,12 +32,14 @@ impl Input { } pub async fn get_input(config: Cfg) -> Result<(), Box> { - let mut user_input = String::new(); - stdin() - .read_line(&mut user_input) - .expect("ERROR"); + let mut args: Vec = env::args().collect(); + dbg!(&args); + args.reverse(); + args.pop(); + args.reverse(); + dbg!(&args); - let input = Input::from(user_input.trim().to_string())?; + let input = Input::from(args.join(" "))?; match input.command.as_str() { "mod" => { @@ -49,6 +51,12 @@ pub async fn get_input(config: Cfg) -> Result<(), Box> { "update" => { update(config).await }, + "setup" => { + setup(config).await + }, + "download" => { + download(config).await + }, _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), } } -- cgit v1.2.3