diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/apis/modrinth.rs | 66 | ||||
-rw-r--r-- | src/commands/io.rs | 2 | ||||
-rw-r--r-- | src/config.rs | 29 | ||||
-rw-r--r-- | src/error.rs | 9 | ||||
-rw-r--r-- | src/lib.rs | 111 | ||||
-rw-r--r-- | src/main.rs | 18 |
6 files changed, 184 insertions, 51 deletions
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs index 9afe7f3..13e7a6d 100644 --- a/src/apis/modrinth.rs +++ b/src/apis/modrinth.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use chrono::{DateTime, FixedOffset}; | 1 | use chrono::{DateTime, FixedOffset}; |
2 | use reqwest::Client; | 2 | use reqwest::Client; |
3 | use serde::Deserialize; | 3 | use serde::{Deserialize, Serialize}; |
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | error::{ErrorType, MLError, MLE}, | 6 | error::{ErrorType, MLError, MLE}, |
@@ -113,7 +113,24 @@ pub struct Hash { | |||
113 | pub sha1: String, | 113 | pub sha1: String, |
114 | } | 114 | } |
115 | 115 | ||
116 | async fn get(api: &str, path: String) -> Result<Option<Vec<u8>>, Box<dyn std::error::Error>> { | 116 | #[derive(Debug, Clone, Serialize, Deserialize)] |
117 | pub 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)] | ||
126 | pub enum GameVersionType { | ||
127 | release, | ||
128 | snapshot, | ||
129 | alpha, | ||
130 | beta | ||
131 | } | ||
132 | |||
133 | async 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 | ||
136 | pub async fn project(api: &str, name: &str) -> Project { | 153 | pub 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,7 +161,7 @@ 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 | } |
@@ -161,7 +178,7 @@ pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> { | |||
161 | id, loaderstr, list.mc_version | 178 | id, loaderstr, list.mc_version |
162 | ); | 179 | ); |
163 | 180 | ||
164 | let data = get(api, url).await.unwrap(); | 181 | let data = get(api, &url).await.unwrap(); |
165 | 182 | ||
166 | match data { | 183 | match data { |
167 | Some(data) => serde_json::from_slice(&data).unwrap(), | 184 | Some(data) => serde_json::from_slice(&data).unwrap(), |
@@ -173,7 +190,7 @@ pub async fn versions(api: &str, id: String, list: List) -> Vec<Version> { | |||
173 | pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { | 190 | pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { |
174 | let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); | 191 | let url = format!(r#"versions?ids=["{}"]"#, versions.join(r#"",""#)); |
175 | 192 | ||
176 | let data = get(api, url).await.unwrap().unwrap(); | 193 | let data = get(api, &url).await.unwrap().unwrap(); |
177 | 194 | ||
178 | serde_json::from_slice(&data).unwrap() | 195 | serde_json::from_slice(&data).unwrap() |
179 | } | 196 | } |
@@ -195,39 +212,8 @@ pub fn extract_current_version(versions: Vec<Version>) -> MLE<String> { | |||
195 | } | 212 | } |
196 | } | 213 | } |
197 | 214 | ||
198 | pub enum MCVersionType { | 215 | pub async fn get_game_versions() -> Vec<GameVersion> { |
199 | Release, | 216 | let data = get("https://api.modrinth.com/v2/", "tag/game_version").await.unwrap().unwrap(); |
200 | Latest, | ||
201 | Specific, | ||
202 | } | ||
203 | 217 | ||
204 | #[derive(Debug, Deserialize)] | 218 | serde_json::from_slice(&data).unwrap() |
205 | pub struct MCVersion { | ||
206 | pub version: String, | ||
207 | pub version_type: String, | ||
208 | pub date: String, | ||
209 | pub major: bool, | ||
210 | } | ||
211 | |||
212 | pub async fn get_minecraft_version(api: &str, version: MCVersionType) -> String { | ||
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 | } | 219 | } |
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 cf27257..e1049d1 100644 --- a/src/config.rs +++ b/src/config.rs | |||
@@ -6,12 +6,13 @@ use std::{ | |||
6 | 6 | ||
7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
8 | 8 | ||
9 | use crate::{db::db_setup, error::MLE, Modloader}; | 9 | use 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)] |
12 | pub struct Cfg { | 12 | pub struct Cfg { |
13 | pub data: String, | 13 | pub data: String, |
14 | pub cache: String, | 14 | pub cache: String, |
15 | pub versions: String, | ||
15 | pub defaults: Defaults, | 16 | pub defaults: Defaults, |
16 | pub apis: Apis, | 17 | pub apis: Apis, |
17 | } | 18 | } |
@@ -24,10 +25,11 @@ pub struct Apis { | |||
24 | #[derive(Debug, Clone, Serialize, Deserialize)] | 25 | #[derive(Debug, Clone, Serialize, Deserialize)] |
25 | pub struct Defaults { | 26 | pub struct Defaults { |
26 | pub modloader: Modloader, | 27 | pub modloader: Modloader, |
28 | pub version: VersionLevel, | ||
27 | } | 29 | } |
28 | 30 | ||
29 | impl Cfg { | 31 | impl Cfg { |
30 | pub fn init(path: Option<String>) -> MLE<Self> { | 32 | pub async fn init(path: Option<String>) -> MLE<Self> { |
31 | let configfile = match path.clone() { | 33 | let configfile = match path.clone() { |
32 | Some(p) => String::from(p), | 34 | Some(p) => String::from(p), |
33 | None => dirs::config_dir() | 35 | None => dirs::config_dir() |
@@ -62,6 +64,15 @@ impl Cfg { | |||
62 | Ok(..) => (), | 64 | Ok(..) => (), |
63 | Err(..) => create_database(&datafile)?, | 65 | Err(..) => create_database(&datafile)?, |
64 | }; | 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 | } | ||
65 | Ok(config) | 76 | Ok(config) |
66 | } | 77 | } |
67 | } | 78 | } |
@@ -78,8 +89,10 @@ fn create_config(path: &str) -> MLE<()> { | |||
78 | let default_cfg = Cfg { | 89 | let default_cfg = Cfg { |
79 | data: cache_dir.clone(), | 90 | data: cache_dir.clone(), |
80 | cache: format!("{}/cache", cache_dir), | 91 | cache: format!("{}/cache", cache_dir), |
92 | versions: cache_dir.clone(), | ||
81 | defaults: Defaults { | 93 | defaults: Defaults { |
82 | modloader: Modloader::Fabric | 94 | modloader: Modloader::Fabric, |
95 | version: VersionLevel::Release | ||
83 | }, | 96 | }, |
84 | apis: Apis { | 97 | apis: Apis { |
85 | modrinth: String::from("https://api.modrinth.com/v2/"), | 98 | modrinth: String::from("https://api.modrinth.com/v2/"), |
@@ -112,3 +125,13 @@ fn create_cache(path: &str) -> MLE<()> { | |||
112 | println!(" ✓"); | 125 | println!(" ✓"); |
113 | Ok(()) | 126 | Ok(()) |
114 | } | 127 | } |
128 | |||
129 | async 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/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 | ||
107 | impl 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 | |||
105 | impl MLError { | 114 | impl MLError { |
106 | pub fn new(etype: ErrorType, message: &str) -> Self { | 115 | pub fn new(etype: ErrorType, message: &str) -> Self { |
107 | Self { | 116 | Self { |
@@ -6,9 +6,10 @@ pub mod db; | |||
6 | pub mod error; | 6 | pub mod error; |
7 | pub mod files; | 7 | pub mod files; |
8 | 8 | ||
9 | use std::fmt::Display; | 9 | use std::{fmt::Display, fs::{File, remove_file, self}, io::{Write, Read}, time::Duration}; |
10 | 10 | ||
11 | pub use apis::*; | 11 | pub use apis::*; |
12 | use apis::modrinth::{get_game_versions, GameVersion, GameVersionType}; | ||
12 | pub use commands::*; | 13 | pub use commands::*; |
13 | use error::{ErrorType, MLError, MLE}; | 14 | use error::{ErrorType, MLError, MLE}; |
14 | use serde::{Deserialize, Serialize, de::Visitor}; | 15 | use serde::{Deserialize, Serialize, de::Visitor}; |
@@ -77,3 +78,111 @@ impl<'de> Deserialize<'de> for Modloader { | |||
77 | deserializer.deserialize_identifier(FieldVisitor) | 78 | deserializer.deserialize_identifier(FieldVisitor) |
78 | } | 79 | } |
79 | } | 80 | } |
81 | |||
82 | #[derive(Debug, Clone)] | ||
83 | pub enum VersionLevel { | ||
84 | Release, | ||
85 | Snapshot, | ||
86 | Version(String) | ||
87 | } | ||
88 | |||
89 | /// Checks if update needed (time) | ||
90 | /// if yes: get versions, update | ||
91 | pub async fn check_game_versions(path: &str, force: bool) -> MLE<()> { | ||
92 | let creation_time = fs::metadata(path)?.created()?; | ||
93 | if !force && creation_time.elapsed().unwrap() < Duration::from_secs(60 * 60 * 24) { return Ok(()); } | ||
94 | print!("Update minecraft versions"); | ||
95 | std::io::stdout().flush()?; | ||
96 | let versions = get_game_versions().await; | ||
97 | remove_file(path)?; | ||
98 | let mut file = File::create(path)?; | ||
99 | file.write_all(&serde_json::to_string_pretty(&versions)?.as_bytes())?; | ||
100 | println!(" ✓"); | ||
101 | Ok(()) | ||
102 | } | ||
103 | |||
104 | /// Loads game versions | ||
105 | pub fn load_game_versions(path: &str) -> MLE<Vec<GameVersion>> { | ||
106 | let mut file = File::open(path)?; | ||
107 | let mut data = String::new(); | ||
108 | file.read_to_string(&mut data)?; | ||
109 | let versions: Vec<GameVersion> = serde_json::from_str(&data)?; | ||
110 | Ok(versions) | ||
111 | } | ||
112 | |||
113 | impl VersionLevel { | ||
114 | |||
115 | pub fn from(str: &str) -> Self { | ||
116 | match str { | ||
117 | "release" => VersionLevel::Release, | ||
118 | "snapshot" => VersionLevel::Snapshot, | ||
119 | _ => VersionLevel::Version(String::from(str)), | ||
120 | } | ||
121 | } | ||
122 | |||
123 | pub fn get(self, versions_path: &str) -> MLE<String> { | ||
124 | let path = format!("{}/versions.json", versions_path); | ||
125 | let mut versions = load_game_versions(&path)?.into_iter(); | ||
126 | |||
127 | match self { | ||
128 | VersionLevel::Release => { | ||
129 | let release = versions.find(|ver| ver.version_type == GameVersionType::release).unwrap(); | ||
130 | println!("{:?}", release); | ||
131 | Ok(release.version) | ||
132 | }, | ||
133 | VersionLevel::Snapshot => { | ||
134 | let snapshot = versions.find(|ver| ver.version_type == GameVersionType::snapshot).unwrap(); | ||
135 | println!("{:?}", snapshot); | ||
136 | Ok(snapshot.version) | ||
137 | }, | ||
138 | VersionLevel::Version(v) => { | ||
139 | if versions.find(|ver| ver.version == v).is_some() { | ||
140 | Ok(v) | ||
141 | } else { | ||
142 | Err(MLError::new(ErrorType::ConfigError, "unknown minecraft version")) | ||
143 | } | ||
144 | }, | ||
145 | } | ||
146 | } | ||
147 | } | ||
148 | |||
149 | impl Serialize for VersionLevel { | ||
150 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
151 | where | ||
152 | S: serde::Serializer { | ||
153 | match self { | ||
154 | VersionLevel::Release => serializer.serialize_str("release"), | ||
155 | VersionLevel::Snapshot => serializer.serialize_str("snapshot"), | ||
156 | VersionLevel::Version(v) => serializer.serialize_str(v), | ||
157 | } | ||
158 | } | ||
159 | } | ||
160 | |||
161 | impl<'de> Deserialize<'de> for VersionLevel { | ||
162 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
163 | where | ||
164 | D: serde::Deserializer<'de>, | ||
165 | { | ||
166 | struct FieldVisitor; | ||
167 | |||
168 | impl<'de> Visitor<'de> for FieldVisitor { | ||
169 | type Value = VersionLevel; | ||
170 | |||
171 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
172 | formatter.write_str("`fabric`, `forge` or `quilt`") | ||
173 | } | ||
174 | |||
175 | fn visit_str<E>(self, v: &str) -> Result<VersionLevel, E> | ||
176 | where | ||
177 | E: serde::de::Error, { | ||
178 | match v { | ||
179 | "release" => Ok(VersionLevel::Release), | ||
180 | "snapshot" => Ok(VersionLevel::Snapshot), | ||
181 | _ => Ok(VersionLevel::Version(String::from(v))) | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | deserializer.deserialize_identifier(FieldVisitor) | ||
187 | } | ||
188 | } | ||
diff --git a/src/main.rs b/src/main.rs index 82c0ade..93da718 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -3,7 +3,7 @@ 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, check_game_versions, VersionLevel, |
7 | }; | 7 | }; |
8 | 8 | ||
9 | //TODO implement remote sql db | 9 | //TODO implement remote sql db |
@@ -18,6 +18,10 @@ struct Cli { | |||
18 | /// config file path | 18 | /// config file path |
19 | #[arg(short, long)] | 19 | #[arg(short, long)] |
20 | config: Option<String>, | 20 | config: Option<String>, |
21 | |||
22 | /// Force GameVersion update | ||
23 | #[arg(long)] | ||
24 | force_gameupdate: bool, | ||
21 | } | 25 | } |
22 | 26 | ||
23 | #[derive(Subcommand)] | 27 | #[derive(Subcommand)] |
@@ -72,6 +76,7 @@ enum Commands { | |||
72 | /// the list you want to export | 76 | /// the list you want to export |
73 | list: Option<String>, | 77 | list: Option<String>, |
74 | }, | 78 | }, |
79 | Test | ||
75 | } | 80 | } |
76 | 81 | ||
77 | #[derive(Subcommand)] | 82 | #[derive(Subcommand)] |
@@ -147,7 +152,8 @@ enum ListCommands { | |||
147 | async fn main() { | 152 | async fn main() { |
148 | let cli = Cli::parse(); | 153 | let cli = Cli::parse(); |
149 | 154 | ||
150 | let config = Cfg::init(cli.config).unwrap(); | 155 | let config = Cfg::init(cli.config).await.unwrap(); |
156 | check_game_versions(format!("{}/versions.json", config.versions).as_str(), cli.force_gameupdate).await.unwrap(); | ||
151 | 157 | ||
152 | match cli.command { | 158 | match cli.command { |
153 | Commands::Mod { command } => { | 159 | Commands::Mod { command } => { |
@@ -204,11 +210,10 @@ async fn main() { | |||
204 | None => config.clone().defaults.modloader, | 210 | None => config.clone().defaults.modloader, |
205 | }; | 211 | }; |
206 | 212 | ||
213 | let versions_path = &config.versions; | ||
207 | let ver = match version { | 214 | let ver = match version { |
208 | Some(ver) => ver, | 215 | Some(ver) => VersionLevel::from(&ver).get(versions_path).unwrap(), |
209 | //TODO get latest version | 216 | None => config.clone().defaults.version.get(versions_path).unwrap(), |
210 | //TODO impl config for specific version or latest or latest snap | ||
211 | None => "1.19.4".to_string(), | ||
212 | }; | 217 | }; |
213 | 218 | ||
214 | list_add(config, id, ver, ml, directory) | 219 | list_add(config, id, ver, ml, directory) |
@@ -262,6 +267,7 @@ async fn main() { | |||
262 | import(config, filestr, download).await | 267 | import(config, filestr, download).await |
263 | } | 268 | } |
264 | Commands::Export { list } => export(config, list), | 269 | Commands::Export { list } => export(config, list), |
270 | Commands::Test => Ok(()), | ||
265 | } | 271 | } |
266 | .unwrap(); | 272 | .unwrap(); |
267 | } | 273 | } |