use std::{io::{Error, ErrorKind, Write}, fs::File}; use reqwest::Client; use futures_util::StreamExt; use crate::{config::Cfg, modrinth::{projects, Project, versions, extract_current_version, Version}, get_current_list, db::{userlist_get_all_ids, get_versions, get_list_version, change_list_versions}, List}; pub async fn update(config: Cfg) -> Result<(), Box> { let current_list = get_current_list(config.clone())?; let mods = userlist_get_all_ids(config.clone(), current_list.clone().id)?; let mut versions = get_versions(config.clone(), mods.clone())?; versions.sort_by_key(|ver| ver.mod_id.clone()); let mut projects = projects(String::from(&config.apis.modrinth), mods).await; projects.sort_by_key(|pro| pro.id.clone()); let mut updatestack: Vec = vec![]; for (index, project) in projects.into_iter().enumerate() { let current_version = &versions[index]; let p_id = String::from(&project.id); let v_id = ¤t_version.mod_id; if &p_id != v_id { return Err(Box::new(Error::new(ErrorKind::Other, "SORTING_ERROR"))) }; if project.versions.join("|") != current_version.versions { updatestack.push(match specific_update(config.clone(), current_list.clone(), project).await { Ok(ver) => ver, //TODO handle errors (only continue on "NO_UPDATE_AVAILABLE") Err(_) => { continue; }, }); }; }; //println!("{:?}", updatestack); download_updates(config, updatestack).await?; Ok(()) } async fn specific_update(config: Cfg, list: List, project: Project) -> Result> { print!("Checking update for '{}' in {}", project.title, list.id); let applicable_versions = versions(String::from(&config.apis.modrinth), String::from(&project.id), list.clone()).await; let mut versions: Vec = vec![]; for ver in &applicable_versions { versions.push(String::from(&ver.id)); } let mut current: Vec = vec![]; if versions.join("|") != get_list_version(config.clone(), list.clone(), String::from(&project.id))? { //get new versions print!(" | getting new version"); let current_str = extract_current_version(applicable_versions.clone())?; current.push(applicable_versions.into_iter().find(|ver| ver.id == current_str).unwrap()); change_list_versions(config, list, current_str, versions, project.id)?; } if current.is_empty() { return Err(Box::new(Error::new(ErrorKind::NotFound, "NO_UPDATE_AVAILABLE"))) }; println!(" | ✔️"); Ok(current[0].clone()) } async fn download_updates(config: Cfg, versions: Vec) -> Result> { let dl_path = String::from(&config.downloads); for ver in versions { let primary_file = ver.files.into_iter().find(|file| file.primary).unwrap(); let dl_path_file = format!("{}/{}", config.downloads, primary_file.filename); println!("Downloading {}", primary_file.url); let res = Client::new() .get(String::from(&primary_file.url)) .send() .await .or(Err(format!("Failed to GET from '{}'", &primary_file.url)))?; // 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) } #[tokio::test] async fn download_updates_test() { use crate::{modrinth::{Version, VersionFile, Hash, VersionType}, config::{Cfg, Apis}}; let config = Cfg { data: "...".to_string(), clean_remove: false, downloads: "./dl".to_string(), apis: Apis { modrinth: "...".to_string() } }; let versions = vec![Version { id: "dEqtGnT9".to_string(), project_id: "kYuIpRLv".to_string(), author_id: "Qnt13hO8".to_string(), featured: true, name: "1.2.2-1.19 - Fabric".to_string(), version_number: "1.2.2-1.19".to_string(), changelog: None, date_published: "2022-11-02T17:41:43.072267Z".to_string(), downloads: 58, version_type: VersionType::release, files: vec![VersionFile { hashes: Hash { sha1: "fdc6dc39427fc92cc1d7ad8b275b5b83325e712b".to_string(), sha512: "5b372f00d6e5d6a5ef225c3897826b9f6a2be5506905f7f71b9e939779765b41be6f2a9b029cfc752ad0751d0d2d5f8bb4544408df1363eebdde15641e99a849".to_string() }, url: "https://cdn.modrinth.com/data/kYuIpRLv/versions/dEqtGnT9/waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(), filename: "waveycapes-fabric-1.2.2-mc1.19.2.jar".to_string(), primary: true, size: 323176 }], game_versions: vec![ "1.19".to_string(), "1.19.1".to_string(), "1.19.2".to_string() ], loaders: vec![ "fabric".to_string() ] }]; assert_eq!(download_updates(config, versions).await.unwrap(), "./dl") }