diff options
author | fxqnlr <[email protected]> | 2022-11-04 23:41:21 +0100 |
---|---|---|
committer | fxqnlr <[email protected]> | 2022-11-04 23:41:21 +0100 |
commit | 5d50f446a1a4612c0c931bdbc61f945760392f29 (patch) | |
tree | 44414994e19b41979a8a939b120ada0d1aa8a13a /src | |
parent | 96cc5257de09682df345e768dc2a91303f9b36c9 (diff) | |
download | modlist-5d50f446a1a4612c0c931bdbc61f945760392f29.tar modlist-5d50f446a1a4612c0c931bdbc61f945760392f29.tar.gz modlist-5d50f446a1a4612c0c931bdbc61f945760392f29.zip |
"finished" update, added some tests
Diffstat (limited to 'src')
-rw-r--r-- | src/apis/modrinth.rs | 23 | ||||
-rw-r--r-- | src/commands/modification.rs | 11 | ||||
-rw-r--r-- | src/commands/update.rs | 140 | ||||
-rw-r--r-- | src/config.rs | 1 | ||||
-rw-r--r-- | src/db.rs | 32 | ||||
-rw-r--r-- | src/input.rs | 10 |
6 files changed, 183 insertions, 34 deletions
diff --git a/src/apis/modrinth.rs b/src/apis/modrinth.rs index 0c3eca5..c71b47f 100644 --- a/src/apis/modrinth.rs +++ b/src/apis/modrinth.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | use std::io::{Error, ErrorKind}; | ||
2 | use chrono::{DateTime, FixedOffset}; | ||
1 | use serde::Deserialize; | 3 | use serde::Deserialize; |
2 | 4 | ||
3 | use crate::{Modloader, List}; | 5 | use crate::{Modloader, List}; |
@@ -153,3 +155,24 @@ pub async fn versions(api: String, id: String, list: List) -> Vec<Version> { | |||
153 | 155 | ||
154 | serde_json::from_slice(&data.await.unwrap()).unwrap() | 156 | serde_json::from_slice(&data.await.unwrap()).unwrap() |
155 | } | 157 | } |
158 | |||
159 | pub fn extract_current_version(versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { | ||
160 | match versions.len() { | ||
161 | 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))), | ||
162 | //TODO compare publish dates | ||
163 | 1.. => { | ||
164 | let mut times: Vec<(String, DateTime<FixedOffset>)> = vec![]; | ||
165 | for ver in versions { | ||
166 | let stamp = DateTime::parse_from_rfc3339(&ver.date_published)?; | ||
167 | times.push((ver.id, stamp)) | ||
168 | } | ||
169 | dbg!(×); | ||
170 | times.sort_by_key(|t| t.1); | ||
171 | times.reverse(); | ||
172 | dbg!(×); | ||
173 | println!("CW: {}", times[0].0); | ||
174 | Ok(times[0].0.to_string()) | ||
175 | }, | ||
176 | _ => panic!("available_versions should never be negative"), | ||
177 | } | ||
178 | } | ||
diff --git a/src/commands/modification.rs b/src/commands/modification.rs index 43e2180..b90c82c 100644 --- a/src/commands/modification.rs +++ b/src/commands/modification.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::io::{Error, ErrorKind}; |
2 | 2 | ||
3 | use crate::{modrinth::{project, versions, Version}, config::Cfg, db::{insert_mod, remove_mod_from_list, get_mod_id, insert_mod_in_list, get_mods, get_mods_from_list}, input::Input, get_current_list}; | 3 | use crate::{modrinth::{project, versions, extract_current_version}, config::Cfg, db::{insert_mod, remove_mod_from_list, get_mod_id, insert_mod_in_list, get_mods, get_mods_from_list}, input::Input, get_current_list}; |
4 | 4 | ||
5 | pub async fn modification(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { | 5 | pub async fn modification(config: Cfg, args: Option<Vec<String>>) -> Result<(), Box<dyn std::error::Error>> { |
6 | 6 | ||
@@ -77,12 +77,3 @@ fn remove(config: Cfg, args: Vec<String>) -> Result<(), Box<dyn std::error::Erro | |||
77 | Ok(()) => Ok(()), | 77 | Ok(()) => Ok(()), |
78 | } | 78 | } |
79 | } | 79 | } |
80 | |||
81 | fn extract_current_version(versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { | ||
82 | match versions.len() { | ||
83 | 0 => Err(Box::new(Error::new(ErrorKind::NotFound, "NO_VERSIONS_AVAILABLE"))), | ||
84 | //TODO compare publish dates | ||
85 | 1.. => Ok(versions[0].id.to_string()), | ||
86 | _ => panic!("available_versions should never be negative"), | ||
87 | } | ||
88 | } | ||
diff --git a/src/commands/update.rs b/src/commands/update.rs index 14c37ec..6275bce 100644 --- a/src/commands/update.rs +++ b/src/commands/update.rs | |||
@@ -1,40 +1,138 @@ | |||
1 | use std::io::{Error, ErrorKind}; | 1 | use std::{io::{Error, ErrorKind, Write}, fs::File}; |
2 | 2 | ||
3 | use crate::{config::Cfg, modrinth::projects, get_current_list, db::{get_mods_from_list, get_versions}}; | 3 | use reqwest::Client; |
4 | |||
5 | use futures_util::StreamExt; | ||
6 | |||
7 | use crate::{config::Cfg, modrinth::{projects, Project, versions, extract_current_version, Version}, get_current_list, db::{get_mods_from_list, get_versions, get_list_version, change_list_versions}, List}; | ||
4 | 8 | ||
5 | pub async fn update(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { | 9 | pub async fn update(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { |
6 | 10 | ||
7 | let current_list = get_current_list(config.clone())?; | 11 | let current_list = get_current_list(config.clone())?; |
8 | 12 | ||
9 | let mods = get_mods_from_list(config.clone(), current_list)?; | 13 | let mods = get_mods_from_list(config.clone(), current_list.clone())?; |
14 | |||
15 | let mut versions = get_versions(config.clone(), mods.clone())?; | ||
16 | versions.sort_by_key(|ver| ver.mod_id.clone()); | ||
10 | 17 | ||
11 | let mut projects = projects(String::from(&config.apis.modrinth), mods.clone()).await; | 18 | let mut projects = projects(String::from(&config.apis.modrinth), mods).await; |
19 | projects.sort_by_key(|pro| pro.id.clone()); | ||
12 | 20 | ||
13 | let mut versions = get_versions(config, mods)?; | 21 | let mut updatestack: Vec<Version> = vec![]; |
22 | for (index, project) in projects.into_iter().enumerate() { | ||
23 | let current_version = &versions[index]; | ||
24 | |||
25 | let p_id = String::from(&project.id); | ||
26 | let v_id = ¤t_version.mod_id; | ||
27 | |||
28 | if &p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "SORTING_ERROR"))) }; | ||
29 | |||
30 | if project.versions.join("|") != current_version.versions { | ||
31 | updatestack.push(match specific_update(config.clone(), current_list.clone(), project).await { | ||
32 | Ok(ver) => ver, | ||
33 | //TODO handle errors (only continue on "NO_UPDATE_AVAILABLE") | ||
34 | Err(_) => { continue; }, | ||
35 | }); | ||
36 | }; | ||
37 | }; | ||
38 | //println!("{:?}", updatestack); | ||
39 | |||
40 | //download_updates(config, updatestack).await?; | ||
41 | |||
42 | Ok(()) | ||
43 | } | ||
44 | |||
45 | async fn specific_update(config: Cfg, list: List, project: Project) -> Result<Version, Box<dyn std::error::Error>> { | ||
46 | print!("Checking update for '{}' in {}", project.title, list.id); | ||
14 | 47 | ||
15 | projects.sort_by_key(|p| p.id.clone()); | 48 | let applicable_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), list.clone()).await; |
49 | |||
50 | let mut versions: Vec<String> = vec![]; | ||
16 | 51 | ||
17 | versions.sort_by_key(|v| v.mod_id.clone()); | 52 | for ver in &applicable_versions { |
53 | versions.push(String::from(&ver.id)); | ||
54 | } | ||
18 | 55 | ||
19 | let mut update_stack: Vec<String> = vec![]; | 56 | let mut current: Vec<Version> = vec![]; |
57 | if versions.join("|") != get_list_version(config.clone(), list.clone(), String::from(&project.id))? { | ||
58 | //get new versions | ||
59 | print!(" | getting new version"); | ||
60 | let current_str = extract_current_version(applicable_versions.clone())?; | ||
61 | current.push(applicable_versions.into_iter().find(|ver| ver.id == current_str).unwrap()); | ||
62 | change_list_versions(config, list, current_str, versions, project.id)?; | ||
63 | } | ||
20 | 64 | ||
21 | for (index, project) in projects.iter().enumerate() { | 65 | if current.is_empty() { return Err(Box::new(Error::new(ErrorKind::NotFound, "NO_UPDATE_AVAILABLE"))) }; |
66 | |||
67 | println!(" | ✔️"); | ||
68 | Ok(current[0].clone()) | ||
69 | } | ||
22 | 70 | ||
23 | let cmp_version = &versions[index]; | 71 | async fn download_updates(config: Cfg, versions: Vec<Version>) -> Result<String, Box<dyn std::error::Error>> { |
24 | 72 | ||
25 | let p_id = &project.id; | 73 | let dl_path = String::from(&config.downloads); |
26 | let v_id = &cmp_version.mod_id; | ||
27 | 74 | ||
28 | if p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "COMPARE_SORTING_ERR"))); }; | 75 | for ver in versions { |
29 | println!("{}:{}", p_id, v_id); | 76 | let primary_file = ver.files.into_iter().find(|file| file.primary).unwrap(); |
77 | let dl_path_file = format!("{}/{}", config.downloads, primary_file.filename); | ||
78 | println!("Downloading {}", primary_file.url); | ||
30 | 79 | ||
31 | if project.versions.join("|") != cmp_version.versions { | 80 | let res = Client::new() |
32 | update_stack.push(String::from(&project.id)); | 81 | .get(String::from(&primary_file.url)) |
33 | }; | 82 | .send() |
34 | }; | 83 | .await |
84 | .or(Err(format!("Failed to GET from '{}'", &primary_file.url)))?; | ||
85 | |||
86 | // download chunks | ||
87 | let mut file = File::create(String::from(&dl_path_file)).or(Err(format!("Failed to create file '{}'", dl_path_file)))?; | ||
88 | let mut stream = res.bytes_stream(); | ||
35 | 89 | ||
36 | //TODO UPDATE | 90 | while let Some(item) = stream.next().await { |
37 | dbg!(update_stack); | 91 | let chunk = item.or(Err("Error while downloading file"))?; |
92 | file.write_all(&chunk) | ||
93 | .or(Err("Error while writing to file"))?; | ||
94 | } | ||
95 | } | ||
38 | 96 | ||
39 | Ok(()) | 97 | Ok(dl_path) |
98 | } | ||
99 | |||
100 | #[tokio::test] | ||
101 | async fn download_updates_test() { | ||
102 | |||
103 | use crate::{modrinth::{Version, VersionFile, Hash, VersionType}, config::{Cfg, Apis}}; | ||
104 | |||
105 | let config = Cfg { data: "...".to_string(), clean_remove: false, downloads: "./dl".to_string(), apis: Apis { modrinth: "...".to_string() } }; | ||
106 | |||
107 | let versions = vec![Version { | ||
108 | id: "dEqtGnT9".to_string(), | ||
109 | project_id: "kYuIpRLv".to_string(), | ||
110 | author_id: "Qnt13hO8".to_string(), | ||
111 | featured: true, | ||
112 | name: "1.2.2-1.19 - Fabric".to_string(), | ||
113 | version_number: "1.2.2-1.19".to_string(), | ||
114 | changelog: None, | ||
115 | date_published: "2022-11-02T17:41:43.072267Z".to_string(), | ||
116 | downloads: 58, | ||
117 | version_type: VersionType::release, | ||
118 | files: vec![VersionFile { | ||
119 | hashes: Hash { | ||
120 | sha1: "fdc6dc39427fc92cc1d7ad8b275b5b83325e712b".to_string(), | ||
121 | sha512: "5b372f00d6e5d6a5ef225c3897826b9f6a2be5506905f7f71b9e939779765b41be6f2a9b029cfc752ad0751d0d2d5f8bb4544408df1363eebdde15641e99a849".to_string() | ||
122 | }, | ||
123 | url: "https://cdn.modrinth.com/data/kYuIpRLv/versions/dEqtGnT9/waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(), | ||
124 | filename: "waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(), | ||
125 | primary: true, | ||
126 | size: 323176 | ||
127 | }], | ||
128 | game_versions: vec![ | ||
129 | "1.19".to_string(), | ||
130 | "1.19.1".to_string(), | ||
131 | "1.19.2".to_string() | ||
132 | ], | ||
133 | loaders: vec![ | ||
134 | "fabric".to_string() | ||
135 | ] | ||
136 | }]; | ||
137 | assert_eq!(download_updates(config, versions).await.unwrap(), "./dl") | ||
40 | } | 138 | } |
diff --git a/src/config.rs b/src/config.rs index 58d399a..ad59963 100644 --- a/src/config.rs +++ b/src/config.rs | |||
@@ -4,6 +4,7 @@ use serde::Deserialize; | |||
4 | #[derive(Debug, Clone, Deserialize)] | 4 | #[derive(Debug, Clone, Deserialize)] |
5 | pub struct Cfg { | 5 | pub struct Cfg { |
6 | pub data: String, | 6 | pub data: String, |
7 | pub downloads: String, | ||
7 | pub clean_remove: bool, | 8 | pub clean_remove: bool, |
8 | pub apis: Apis, | 9 | pub apis: Apis, |
9 | } | 10 | } |
@@ -2,7 +2,7 @@ use std::io::ErrorKind; | |||
2 | 2 | ||
3 | use crate::{Modloader, config::Cfg, List, modrinth::Version, get_modloader}; | 3 | use crate::{Modloader, config::Cfg, List, modrinth::Version, get_modloader}; |
4 | 4 | ||
5 | //TODO use prepared statements | 5 | //TODO use prepared statements / change to rusqlite |
6 | 6 | ||
7 | //MODS | 7 | //MODS |
8 | pub fn insert_mod(config: Cfg, id: String, name: String, versions: Vec<String>) -> Result<(), sqlite::Error> { | 8 | pub fn insert_mod(config: Cfg, id: String, name: String, versions: Vec<String>) -> Result<(), sqlite::Error> { |
@@ -151,6 +151,27 @@ pub fn get_versions(config: Cfg, mods: Vec<String>) -> Result<Vec<DBModlistVersi | |||
151 | Ok(versionmaps) | 151 | Ok(versionmaps) |
152 | } | 152 | } |
153 | 153 | ||
154 | pub fn get_list_version(config: Cfg, list: List, mod_id: String) -> Result<String, Box<dyn std::error::Error>> { | ||
155 | let data = format!("{}/data.db", config.data); | ||
156 | let connection = sqlite::open(data).unwrap(); | ||
157 | |||
158 | let sql = format!("SELECT applicable_versions FROM {} WHERE mod_id = '{}'", list.id, mod_id); | ||
159 | |||
160 | //TODO catch sql errors better | ||
161 | let mut version: String = String::new(); | ||
162 | connection.iterate(sql, |ver| { | ||
163 | if ver.is_empty() { return false; }; | ||
164 | for &(_column, value) in ver.iter() { | ||
165 | version = String::from(value.unwrap()); | ||
166 | } | ||
167 | true | ||
168 | }).unwrap(); | ||
169 | |||
170 | if version.is_empty() { return Err(Box::new(std::io::Error::new(ErrorKind::Other, "NO_MODS_ON_LIST"))); }; | ||
171 | |||
172 | Ok(version) | ||
173 | } | ||
174 | |||
154 | 175 | ||
155 | //LIST | 176 | //LIST |
156 | pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modloader) -> Result<(), sqlite::Error> { | 177 | pub fn insert_list(config: Cfg, id: String, mc_version: String, mod_loader: Modloader) -> Result<(), sqlite::Error> { |
@@ -217,6 +238,15 @@ pub fn get_list(config: Cfg, id: String) -> Result<List, Box<dyn std::error::Err | |||
217 | Ok(List { id, mc_version: String::from(&list[0]), modloader: get_modloader(String::from(&list[1]))? }) | 238 | Ok(List { id, mc_version: String::from(&list[0]), modloader: get_modloader(String::from(&list[1]))? }) |
218 | } | 239 | } |
219 | 240 | ||
241 | pub fn change_list_versions(config: Cfg, list: List, current_version: String, versions: Vec<String>, mod_id: String) -> Result<(), sqlite::Error> { | ||
242 | let data = format!("{}/data.db", config.data); | ||
243 | let connection = sqlite::open(data).unwrap(); | ||
244 | |||
245 | let sql = format!("UPDATE {} SET current_version = '{}', applicable_versions = '{}' WHERE mod_id = '{}'", list.id, current_version, versions.join("|"), mod_id); | ||
246 | |||
247 | connection.execute(sql) | ||
248 | } | ||
249 | |||
220 | //config | 250 | //config |
221 | pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { | 251 | pub fn change_list(config: Cfg, id: String) -> Result<(), sqlite::Error> { |
222 | let data = format!("{}/data.db", config.data); | 252 | let data = format!("{}/data.db", config.data); |
diff --git a/src/input.rs b/src/input.rs index 0c13e67..e0c9ae9 100644 --- a/src/input.rs +++ b/src/input.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | use std::io::{stdin, Error, ErrorKind}; | 1 | use std::io::{stdin, Error, ErrorKind}; |
2 | use crate::{config::Cfg, list, modification, update}; | 2 | use crate::{config::Cfg, list, modification, update}; |
3 | 3 | ||
4 | #[derive(Debug, PartialEq, Eq)] | ||
4 | pub struct Input { | 5 | pub struct Input { |
5 | pub command: String, | 6 | pub command: String, |
6 | pub args: Option<Vec<String>>, | 7 | pub args: Option<Vec<String>>, |
@@ -27,8 +28,6 @@ impl Input { | |||
27 | }, | 28 | }, |
28 | _ => { panic!("This should never happen") } | 29 | _ => { panic!("This should never happen") } |
29 | } | 30 | } |
30 | |||
31 | |||
32 | } | 31 | } |
33 | } | 32 | } |
34 | 33 | ||
@@ -53,3 +52,10 @@ pub async fn get_input(config: Cfg) -> Result<(), Box<dyn std::error::Error>> { | |||
53 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), | 52 | _ => Err(Box::new(Error::new(ErrorKind::InvalidInput, "UNKNOWN_COMMAND"))), |
54 | } | 53 | } |
55 | } | 54 | } |
55 | |||
56 | #[test] | ||
57 | fn input_from() { | ||
58 | let string = String::from("list add test 1.19.2 fabric"); | ||
59 | let input = Input { command: String::from("list"), args: Some(vec![String::from("add"), String::from("test"), String::from("1.19.2"), String::from("fabric")]) }; | ||
60 | assert_eq!(Input::from(string).unwrap(), input); | ||
61 | } | ||