summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/apis/modrinth.rs75
-rw-r--r--src/commands/io.rs2
-rw-r--r--src/config.rs51
-rw-r--r--src/db.rs11
-rw-r--r--src/error.rs9
-rw-r--r--src/lib.rs81
-rw-r--r--src/main.rs22
7 files changed, 182 insertions, 69 deletions
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs
index 9afe7f3..525cc0d 100644
--- a/src/apis/modrinth.rs
+++ b/src/apis/modrinth.rs
@@ -1,10 +1,10 @@
1use chrono::{DateTime, FixedOffset}; 1use chrono::{DateTime, FixedOffset};
2use reqwest::Client; 2use reqwest::Client;
3use serde::Deserialize; 3use serde::{Deserialize, Serialize};
4 4
5use crate::{ 5use crate::{
6 error::{ErrorType, MLError, MLE}, 6 error::{ErrorType, MLError, MLE},
7 List, Modloader, 7 List,
8}; 8};
9 9
10#[derive(Debug, Deserialize, Clone)] 10#[derive(Debug, Deserialize, Clone)]
@@ -113,7 +113,24 @@ pub struct Hash {
113 pub sha1: String, 113 pub sha1: String,
114} 114}
115 115
116async fn get(api: &str, path: String) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> { 116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct GameVersion {
118 pub version: String,
119 pub version_type: GameVersionType,
120 pub date: String,
121 pub major: bool,
122}
123
124#[allow(non_camel_case_types)]
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub enum GameVersionType {
127 release,
128 snapshot,
129 alpha,
130 beta
131}
132
133async fn get(api: &str, path: &str) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> {
117 let url = format!(r#"{}{}"#, api, path); 134 let url = format!(r#"{}{}"#, api, path);
118 135
119 let client = Client::builder() 136 let client = Client::builder()
@@ -135,7 +152,7 @@ async fn get(api: &str, path: String) -> Result<Option<Vec<u8>>, Box<dyn std::er
135 152
136pub async fn project(api: &str, name: &str) -> Project { 153pub async fn project(api: &str, name: &str) -> Project {
137 let url = format!("project/{}", name); 154 let url = format!("project/{}", name);
138 let data = get(api, url).await.unwrap().unwrap(); 155 let data = get(api, &url).await.unwrap().unwrap();
139 156
140 serde_json::from_slice(&data).unwrap() 157 serde_json::from_slice(&data).unwrap()
141} 158}
@@ -144,24 +161,19 @@ pub async fn projects(api: &str, ids: Vec<String>) -> Vec<Project> {
144 let all = ids.join(r#"",""#); 161 let all = ids.join(r#"",""#);
145 let url = format!(r#"projects?ids=["{}"]"#, all); 162 let url = format!(r#"projects?ids=["{}"]"#, all);
146 163
147 let data = get(api, url).await.unwrap().unwrap(); 164 let data = get(api, &url).await.unwrap().unwrap();
148 165
149 serde_json::from_slice(&data).unwrap() 166 serde_json::from_slice(&data).unwrap()
150} 167}
151 168
152///Get applicable versions from mod_id with list context 169///Get applicable versions from mod_id with list context
153pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> { 170pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
154 let loaderstr = match list.modloader {
155 Modloader::Forge => String::from("forge"),
156 Modloader::Fabric => String::from("fabric"),
157 };
158
159 let url = format!( 171 let url = format!(
160 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#, 172 r#"project/{}/version?loaders=["{}"]&game_versions=["{}"]"#,
161 id, loaderstr, list.mc_version 173 id, list.modloader.to_string(), list.mc_version
162 ); 174 );
163 175
164 let data = get(api, url).await.unwrap(); 176 let data = get(api, &url).await.unwrap();
165 177
166 match data { 178 match data {
167 Some(data) => serde_json::from_slice(&data).unwrap(), 179 Some(data) => serde_json::from_slice(&data).unwrap(),
@@ -173,7 +185,7 @@ pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> {
173pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { 185pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> {
174 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); 186 let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#));
175 187
176 let data = get(api, url).await.unwrap().unwrap(); 188 let data = get(api, &url).await.unwrap().unwrap();
177 189
178 serde_json::from_slice(&data).unwrap() 190 serde_json::from_slice(&data).unwrap()
179} 191}
@@ -195,39 +207,8 @@ pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> {
195 } 207 }
196} 208}
197 209
198pub enum MCVersionType { 210pub async fn get_game_versions() -> Vec<GameVersion> {
199 Release, 211 let data = get("https://api.modrinth.com/v2/", "tag/game_version").await.unwrap().unwrap();
200 Latest,
201 Specific,
202}
203
204#[derive(Debug, Deserialize)]
205pub struct MCVersion {
206 pub version: String,
207 pub version_type: String,
208 pub date: String,
209 pub major: bool,
210}
211 212
212pub async fn get_minecraft_version(api: &str, version: MCVersionType) -> String { 213 serde_json::from_slice(&data).unwrap()
213 let data = get(api, String::from("tag/game_version"))
214 .await
215 .unwrap()
216 .unwrap();
217 let mc_versions: Vec<MCVersion> = serde_json::from_slice(&data).unwrap();
218 let ver = match version {
219 MCVersionType::Release => {
220 let mut i = 0;
221 while !mc_versions[i].major {
222 i += 1;
223 }
224 &mc_versions[i]
225 }
226 MCVersionType::Latest => &mc_versions[0],
227 MCVersionType::Specific => {
228 println!("Not inplemented");
229 &mc_versions[0]
230 }
231 };
232 String::from(&ver.version)
233} 214}
diff --git a/src/commands/io.rs b/src/commands/io.rs
index 82b30ce..e072f00 100644
--- a/src/commands/io.rs
+++ b/src/commands/io.rs
@@ -92,7 +92,7 @@ pub async fn import(config: Cfg, file_str: String, direct_download: bool) -> MLE
92 mod_ids.push(IDSelector::ModificationID(String::from(mod_id))); 92 mod_ids.push(IDSelector::ModificationID(String::from(mod_id)));
93 } 93 }
94 //TODO impl set_version and good direct download 94 //TODO impl set_version and good direct download
95 //TODO impl all at once, dafuck 95 //TODO impl all at once, dafuck ?done?
96 mod_add(config.clone(), mod_ids, list, direct_download, false).await?; 96 mod_add(config.clone(), mod_ids, list, direct_download, false).await?;
97 } 97 }
98 Ok(()) 98 Ok(())
diff --git a/src/config.rs b/src/config.rs
index 61db1c7..e1049d1 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,12 +6,14 @@ use std::{
6 6
7use serde::{Deserialize, Serialize}; 7use serde::{Deserialize, Serialize};
8 8
9use crate::{db::db_setup, error::MLE}; 9use crate::{db::db_setup, error::MLE, Modloader, VersionLevel, check_game_versions};
10 10
11#[derive(Debug, Clone, Serialize, Deserialize)] 11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Cfg { 12pub struct Cfg {
13 pub data: String, 13 pub data: String,
14 pub cache: String, 14 pub cache: String,
15 pub versions: String,
16 pub defaults: Defaults,
15 pub apis: Apis, 17 pub apis: Apis,
16} 18}
17 19
@@ -20,13 +22,20 @@ pub struct Apis {
20 pub modrinth: String, 22 pub modrinth: String,
21} 23}
22 24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct Defaults {
27 pub modloader: Modloader,
28 pub version: VersionLevel,
29}
30
23impl Cfg { 31impl Cfg {
24 pub fn init(path: Option<String>) -> MLE<Self> { 32 pub async fn init(path: Option<String>) -> MLE<Self> {
25 let configfile = match path.clone() { 33 let configfile = match path.clone() {
26 Some(p) => String::from(p), 34 Some(p) => String::from(p),
27 None => dirs::config_dir() 35 None => dirs::config_dir()
28 .unwrap() 36 .unwrap()
29 .join("modlist.toml") 37 .join("modlist")
38 .join("config.toml")
30 .to_string_lossy() 39 .to_string_lossy()
31 .to_string(), 40 .to_string(),
32 }; 41 };
@@ -50,12 +59,20 @@ impl Cfg {
50 create_cache(&config.cache)?; 59 create_cache(&config.cache)?;
51 }; 60 };
52 //Check database 61 //Check database
53 //TODO check file
54 let datafile = format!("{}/data.db", config.data); 62 let datafile = format!("{}/data.db", config.data);
55 match File::open(&datafile) { 63 match File::open(&datafile) {
56 Ok(..) => (), 64 Ok(..) => (),
57 Err(..) => create_database(&datafile)?, 65 Err(..) => create_database(&datafile)?,
58 }; 66 };
67 //Check versions
68 let versionfile = format!("{}/versions.json", config.versions);
69 match File::open(&versionfile) {
70 Ok(..) => (),
71 Err(..) => {
72 create_versions_dummy(&versionfile).await?;
73 check_game_versions(&versionfile, true).await?;
74 },
75 }
59 Ok(config) 76 Ok(config)
60 } 77 }
61} 78}
@@ -64,14 +81,24 @@ fn create_config(path: &str) -> MLE<()> {
64 print!("No config file found, create default"); 81 print!("No config file found, create default");
65 //Force flush of stdout, else print! doesn't print instantly 82 //Force flush of stdout, else print! doesn't print instantly
66 std::io::stdout().flush()?; 83 std::io::stdout().flush()?;
84 let cache_dir = dirs::cache_dir()
85 .unwrap()
86 .join("modlist")
87 .to_string_lossy()
88 .to_string();
67 let default_cfg = Cfg { 89 let default_cfg = Cfg {
68 //TODO get home dir 90 data: cache_dir.clone(),
69 data: String::from("$HOME/.cache/modlist/"), 91 cache: format!("{}/cache", cache_dir),
70 cache: String::from("$HOME/.cache/modlist/cache"), 92 versions: cache_dir.clone(),
93 defaults: Defaults {
94 modloader: Modloader::Fabric,
95 version: VersionLevel::Release
96 },
71 apis: Apis { 97 apis: Apis {
72 modrinth: String::from("https://api.modrinth.com/v2/"), 98 modrinth: String::from("https://api.modrinth.com/v2/"),
73 }, 99 },
74 }; 100 };
101 create_dir_all(path.split("config.toml").collect::<Vec<&str>>()[0])?;
75 let mut file = File::create(path)?; 102 let mut file = File::create(path)?;
76 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?; 103 file.write_all(toml::to_string(&default_cfg)?.as_bytes())?;
77 println!(" ✓"); 104 println!(" ✓");
@@ -98,3 +125,13 @@ fn create_cache(path: &str) -> MLE<()> {
98 println!(" ✓"); 125 println!(" ✓");
99 Ok(()) 126 Ok(())
100} 127}
128
129async fn create_versions_dummy(path: &str) -> MLE<()> {
130 print!("No version file found, create dummy");
131 //Force flush of stdout, else print! doesn't print instantly
132 std::io::stdout().flush()?;
133
134 File::create(path)?;
135 println!(" ✓");
136 Ok(())
137}
diff --git a/src/db.rs b/src/db.rs
index abfe1dd..6c7e4f8 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -50,8 +50,6 @@ pub fn mods_get_all_ids(config: Cfg) -> Result<Vec<String>, Box<dyn std::error::
50/// 50///
51///Will return `MLError` when no mod id is found 51///Will return `MLError` when no mod id is found
52pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> { 52pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
53 //TODO check if "slug" is id
54
55 let data = format!("{}/data.db", data); 53 let data = format!("{}/data.db", data);
56 let connection = Connection::open(data)?; 54 let connection = Connection::open(data)?;
57 55
@@ -64,6 +62,15 @@ pub fn mods_get_id(data: &str, slug: &str) -> MLE<String> {
64 for id in id_iter { 62 for id in id_iter {
65 mod_id = id?; 63 mod_id = id?;
66 } 64 }
65 //get from id if no slug found
66 if mod_id.is_empty() {
67 let mut stmt = connection.prepare("SELECT id FROM mods WHERE id = ?")?;
68 let id_iter = stmt.query_map([slug], |row| row.get::<usize, String>(0))?;
69
70 for id in id_iter {
71 mod_id = id?;
72 }
73 }
67 //get from title if no id found from slug 74 //get from title if no id found from slug
68 if mod_id.is_empty() { 75 if mod_id.is_empty() {
69 let mut stmt = connection.prepare("SELECT id FROM mods WHERE title = ?")?; 76 let mut stmt = connection.prepare("SELECT id FROM mods WHERE title = ?")?;
diff --git a/src/error.rs b/src/error.rs
index bd6e3da..e6afeaa 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -20,6 +20,7 @@ pub enum ErrorType {
20 LibSql, 20 LibSql,
21 LibReq, 21 LibReq,
22 LibChrono, 22 LibChrono,
23 LibJson,
23 IoError, 24 IoError,
24 Other, 25 Other,
25} 26}
@@ -42,6 +43,7 @@ impl fmt::Display for MLError {
42 ErrorType::LibSql => write!(f, "SQL: {}", self.message), 43 ErrorType::LibSql => write!(f, "SQL: {}", self.message),
43 ErrorType::LibReq => write!(f, "REQWEST"), 44 ErrorType::LibReq => write!(f, "REQWEST"),
44 ErrorType::LibChrono => write!(f, "Chrono error: {}", self.message), 45 ErrorType::LibChrono => write!(f, "Chrono error: {}", self.message),
46 ErrorType::LibJson => write!(f, "JSON: {}", self.message),
45 ErrorType::IoError => write!(f, "IO"), 47 ErrorType::IoError => write!(f, "IO"),
46 ErrorType::Other => write!(f, "OTHER"), 48 ErrorType::Other => write!(f, "OTHER"),
47 } 49 }
@@ -102,6 +104,13 @@ impl From<std::io::Error> for MLError {
102 } 104 }
103} 105}
104 106
107impl From<serde_json::error::Error> for MLError {
108 fn from(value: serde_json::error::Error) -> Self {
109 Self { etype: ErrorType::LibJson, message: value.to_string() }
110 }
111
112}
113
105impl MLError { 114impl MLError {
106 pub fn new(etype: ErrorType, message: &str) -> Self { 115 pub fn new(etype: ErrorType, message: &str) -> Self {
107 Self { 116 Self {
diff --git a/src/lib.rs b/src/lib.rs
index 185edd7..f18bdfb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,16 +6,22 @@ pub mod db;
6pub mod error; 6pub mod error;
7pub mod files; 7pub mod files;
8 8
9use std::fmt::Display; 9use std::{fmt::Display, fs::{File, remove_file, self}, io::{Write, Read}, time::Duration};
10 10
11pub use apis::*; 11pub use apis::*;
12use apis::modrinth::{get_game_versions, GameVersion, GameVersionType};
12pub use commands::*; 13pub use commands::*;
13use error::{ErrorType, MLError, MLE}; 14use error::{ErrorType, MLError, MLE};
15use serde::{Deserialize, Serialize};
14 16
15#[derive(Debug, Clone, PartialEq, Eq)] 17#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
16pub enum Modloader { 18pub enum Modloader {
19 #[serde(rename(serialize = "fabric", deserialize = "fabric"))]
17 Fabric, 20 Fabric,
21 #[serde(rename(serialize = "forge", deserialize = "forge"))]
18 Forge, 22 Forge,
23 #[serde(rename(serialize = "quilt", deserialize = "quilt"))]
24 Quilt,
19} 25}
20 26
21impl Modloader { 27impl Modloader {
@@ -33,6 +39,77 @@ impl Display for Modloader {
33 match self { 39 match self {
34 Modloader::Fabric => write!(f, "fabric"), 40 Modloader::Fabric => write!(f, "fabric"),
35 Modloader::Forge => write!(f, "forge"), 41 Modloader::Forge => write!(f, "forge"),
42 Modloader::Quilt => write!(f, "quilt"),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
48pub enum VersionLevel {
49 #[serde(rename(serialize = "release", deserialize = "release"))]
50 Release,
51 #[serde(rename(serialize = "snapshot", deserialize = "snapshot"))]
52 Snapshot,
53 Version(String)
54}
55
56/// Checks if update needed (time)
57/// if yes: get versions, update
58pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> {
59 let creation_time = fs::metadata(path)?.created()?;
60 if !force && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24) { return Ok(()); }
61 print!("Update minecraft versions");
62 std::io::stdout().flush()?;
63 let versions = get_game_versions().await;
64 remove_file(path)?;
65 let mut file = File::create(path)?;
66 file.write_all(&serde_json::to_string_pretty(&versions)?.as_bytes())?;
67 println!(" ✓");
68 Ok(())
69}
70
71/// Loads game versions from file
72pub fn load_game_versions(path: &str) -> MLE<Vec<GameVersion>> {
73 let mut file = File::open(path)?;
74 let mut data = String::new();
75 file.read_to_string(&mut data)?;
76 let versions: Vec<GameVersion> = serde_json::from_str(&data)?;
77 Ok(versions)
78}
79
80impl VersionLevel {
81
82 pub fn from(str: &str) -> Self {
83 match str {
84 "release" => VersionLevel::Release,
85 "snapshot" => VersionLevel::Snapshot,
86 _ => VersionLevel::Version(String::from(str)),
87 }
88 }
89
90 pub async fn get(self, versions_path: &str, force_update: bool) -> MLE<String> {
91 let path = format!("{}/versions.json", versions_path);
92 check_game_versions(&path, force_update).await?;
93 let mut versions = load_game_versions(&path)?.into_iter();
94
95 match self {
96 VersionLevel::Release => {
97 let release = versions.find(|ver| ver.version_type == GameVersionType::release).unwrap();
98 println!("{:?}", release);
99 Ok(release.version)
100 },
101 VersionLevel::Snapshot => {
102 let snapshot = versions.find(|ver| ver.version_type == GameVersionType::snapshot).unwrap();
103 println!("{:?}", snapshot);
104 Ok(snapshot.version)
105 },
106 VersionLevel::Version(v) => {
107 if versions.find(|ver| ver.version == v).is_some() {
108 Ok(v)
109 } else {
110 Err(MLError::new(ErrorType::ConfigError, "unknown minecraft version"))
111 }
112 },
36 } 113 }
37 } 114 }
38} 115}
diff --git a/src/main.rs b/src/main.rs
index 53cbe71..3bc2ba0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,11 +3,9 @@ use modlist::{
3 config::Cfg, 3 config::Cfg,
4 db::{config_get_current_list, lists_get, lists_get_all_ids}, 4 db::{config_get_current_list, lists_get, lists_get_all_ids},
5 download, export, get_current_list, import, list_add, list_change, list_remove, list_version, 5 download, export, get_current_list, import, list_add, list_change, list_remove, list_version,
6 mod_add, mod_remove, update, IDSelector, List, Modloader, 6 mod_add, mod_remove, update, IDSelector, List, Modloader, VersionLevel,
7}; 7};
8 8
9//TODO implement remote sql db
10
11//TODO make default list optional 9//TODO make default list optional
12#[derive(Parser)] 10#[derive(Parser)]
13#[command(author, version, about)] 11#[command(author, version, about)]
@@ -18,6 +16,10 @@ struct Cli {
18 /// config file path 16 /// config file path
19 #[arg(short, long)] 17 #[arg(short, long)]
20 config: Option<String>, 18 config: Option<String>,
19
20 /// Force GameVersion update
21 #[arg(long)]
22 force_gameupdate: bool,
21} 23}
22 24
23#[derive(Subcommand)] 25#[derive(Subcommand)]
@@ -72,6 +74,7 @@ enum Commands {
72 /// the list you want to export 74 /// the list you want to export
73 list: Option<String>, 75 list: Option<String>,
74 }, 76 },
77 Test
75} 78}
76 79
77#[derive(Subcommand)] 80#[derive(Subcommand)]
@@ -147,7 +150,7 @@ enum ListCommands {
147async fn main() { 150async fn main() {
148 let cli = Cli::parse(); 151 let cli = Cli::parse();
149 152
150 let config = Cfg::init(cli.config).unwrap(); 153 let config = Cfg::init(cli.config).await.unwrap();
151 154
152 match cli.command { 155 match cli.command {
153 Commands::Mod { command } => { 156 Commands::Mod { command } => {
@@ -201,15 +204,13 @@ async fn main() {
201 } => { 204 } => {
202 let ml = match modloader { 205 let ml = match modloader {
203 Some(ml) => Modloader::from(&ml).unwrap(), 206 Some(ml) => Modloader::from(&ml).unwrap(),
204 //TODO add default modloader to config 207 None => config.clone().defaults.modloader,
205 None => Modloader::Fabric,
206 }; 208 };
207 209
210 let versions_path = &config.versions;
208 let ver = match version { 211 let ver = match version {
209 Some(ver) => ver, 212 Some(ver) => VersionLevel::from(&ver).get(versions_path, cli.force_gameupdate).await.unwrap(),
210 //TODO get latest version 213 None => config.clone().defaults.version.get(versions_path, cli.force_gameupdate).await.unwrap(),
211 //TODO impl config for specific version or latest or latest snap
212 None => "1.19.4".to_string(),
213 }; 214 };
214 215
215 list_add(config, id, ver, ml, directory) 216 list_add(config, id, ver, ml, directory)
@@ -263,6 +264,7 @@ async fn main() {
263 import(config, filestr, download).await 264 import(config, filestr, download).await
264 } 265 }
265 Commands::Export { list } => export(config, list), 266 Commands::Export { list } => export(config, list),
267 Commands::Test => Ok(()),
266 } 268 }
267 .unwrap(); 269 .unwrap();
268} 270}