summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--src/apis/modrinth.rs66
-rw-r--r--src/commands/io.rs2
-rw-r--r--src/config.rs29
-rw-r--r--src/error.rs9
-rw-r--r--src/lib.rs111
-rw-r--r--src/main.rs18
8 files changed, 186 insertions, 53 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f25b161..0e93641 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -798,7 +798,7 @@ dependencies = [
798 798
799[[package]] 799[[package]]
800name = "modlist" 800name = "modlist"
801version = "0.13.1" 801version = "0.14.0"
802dependencies = [ 802dependencies = [
803 "chrono", 803 "chrono",
804 "clap", 804 "clap",
diff --git a/Cargo.toml b/Cargo.toml
index 7b6466f..736831d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "modlist" 2name = "modlist"
3version = "0.13.1" 3version = "0.14.0"
4edition = "2021" 4edition = "2021"
5 5
6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
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 @@
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},
@@ -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,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> {
173pub async fn get_raw_versions(api: &str, versions: Vec<String>) -> Vec<Version> { 190pub 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
198pub enum MCVersionType { 215pub 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()
205pub struct MCVersion {
206 pub version: String,
207 pub version_type: String,
208 pub date: String,
209 pub major: bool,
210}
211
212pub 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
7use serde::{Deserialize, Serialize}; 7use serde::{Deserialize, Serialize};
8 8
9use crate::{db::db_setup, error::MLE, Modloader}; 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,
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)]
25pub struct Defaults { 26pub struct Defaults {
26 pub modloader: Modloader, 27 pub modloader: Modloader,
28 pub version: VersionLevel,
27} 29}
28 30
29impl Cfg { 31impl 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
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/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 05d74db..73b5697 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,9 +6,10 @@ 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};
14use serde::{Deserialize, Serialize, de::Visitor}; 15use 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)]
83pub enum VersionLevel {
84 Release,
85 Snapshot,
86 Version(String)
87}
88
89/// Checks if update needed (time)
90/// if yes: get versions, update
91pub 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
105pub 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
113impl 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
149impl 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
161impl<'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 {
147async fn main() { 152async 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}