From 2c84ebb87ecf5eb0b5e663f6da2f918e10c80734 Mon Sep 17 00:00:00 2001 From: Quentin Legot Date: Fri, 6 Oct 2023 23:32:35 +0200 Subject: [PATCH] Added modpack downloading --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/launcher/altarik/mod.rs | 84 +++++++++++++++++++-------- src-tauri/src/launcher/mod.rs | 10 +++- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2b3878c..7c6a2c7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1670,6 +1670,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "sha1", "sha256", "tauri", "tauri-build", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3f4319b..c517e0c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ sha256 = "1.4.0" tokio-tar = "0.3.1" async_zip = { version = "0.0.15", features = ["full"] } tokio-stream = "0.1.14" +sha1 = "0.10.6" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/launcher/altarik/mod.rs b/src-tauri/src/launcher/altarik/mod.rs index d5eeb0a..2f2d6fb 100644 --- a/src-tauri/src/launcher/altarik/mod.rs +++ b/src-tauri/src/launcher/altarik/mod.rs @@ -1,10 +1,11 @@ use std::path::{PathBuf, Path}; -use anyhow::{Result, bail}; +use anyhow::{Result, bail, anyhow}; use reqwest::Client; use serde::{Serialize, Deserialize}; use async_zip::base::read::seek::ZipFileReader; -use tokio::{fs::{OpenOptions, File, self}, sync::mpsc, io::AsyncWriteExt}; +use sha1::{Digest, Sha1}; +use tokio::{fs::{OpenOptions, File, self}, sync::mpsc, io::{AsyncWriteExt, AsyncReadExt}}; use tokio_tar::Archive; use tokio_stream::StreamExt; @@ -86,15 +87,9 @@ impl JavaPlatform { pub async fn download_java(&self, root_path: &Path, reqwest: &Client, log_channel: mpsc::Sender) -> Result<()> { let download_path = root_path.join("runtime").join("download"); let (url, extension) = match ACTUAL_OS { - OSName::Linux => { - (self.linux.clone(), "tar.gz") - }, - OSName::Windows => { - (self.win32.clone(), "zip") - }, - _ => { - bail!("Your current is not supported") - } + OSName::Linux => (self.linux.clone(), "tar.gz"), + OSName::Windows => (self.win32.clone(), "zip"), + _ => bail!("Your current is not supported") }; let url = match url { Some(url) => url, @@ -148,7 +143,7 @@ impl JavaPlatform { Ok(()) } - pub async fn extract_java(&self, root_path: &Path) -> Result<()> { + pub async fn extract_java(&self, root_path: &Path, log_channel: mpsc::Sender) -> Result<()> { let (url, extension) = match ACTUAL_OS { OSName::Linux => { (self.linux.clone(), "tar.gz") @@ -166,10 +161,7 @@ impl JavaPlatform { }; let archive_path = root_path.join("runtime").join("download").join(format!("{}.{}", url.x64.name, extension)); let extract_path = root_path.join("runtime"); - // if !extract_path.exists() { - // fs::create_dir(extract_path.clone()).await?; - // } - extract_zip(archive_path, extract_path).await?; + extract_zip(archive_path, extract_path, log_channel).await?; Ok(()) } @@ -179,14 +171,57 @@ impl JavaPlatform { impl ModsPack { - pub async fn download_mods() { - // TODO + pub async fn download_mods(&self, reqwest: &Client, modpack_dir: PathBuf, log_channel: mpsc::Sender) -> Result<()> { + for index in 0..self.mods.len() { + log_channel.send(ProgressMessage { p_type: "mods".to_string(), current: index, total: self.mods.len() }).await?; + let mod_url = self.mods.get(index).ok_or(anyhow!("Cannot get mod download link"))?; + let sha1 = self.sha1sum.get(index).ok_or(anyhow!("Cannot verify mod integrity"))?; + let filepath = modpack_dir.join(format!("modpack{}.zip", index)); + let should_download = self.should_download_mod(&sha1, &filepath).await?; + if should_download { + println!("Need to download mod {}", mod_url); + let mut res = reqwest.get(mod_url) + .send() + .await?; + let mut file = fs::OpenOptions::new() + .write(true) + .create_new(true) + .append(true) + .open(filepath) + .await?; + while let Some(chunk) = res.chunk().await? { + file.write_all(&chunk).await?; + } + } + } + Ok(()) + } + + async fn should_download_mod(&self, mod_sha1: &&String, filepath: &PathBuf) -> Result { + if filepath.exists() { + let mut hasher = Sha1::new(); + let mut file = File::open(filepath).await?; + let mut content = Vec::new(); + file.read(&mut content).await?; + hasher.update(content); + let hash = hasher.finalize(); + // let b16 = base16ct::upper::encode_string(hash); + if &&format!("{:x}", &hash.clone()) != mod_sha1 { + println!("Correct: {:?}, current: {:X}", mod_sha1, hash); + fs::remove_file(filepath).await?; + Ok(true) + } else { + Ok(false) + } + } else { + Ok(true) + } } } -async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf) -> Result<()> { +async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf, log_channel: mpsc::Sender) -> Result<()> { let file = File::open(archive_path.clone()).await?; let mut archive = Archive::new(file); let mut entries = archive.entries()?; @@ -194,24 +229,27 @@ async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf) -> Result<( let file = entry?; println!("{:?}", archive_path); println!("{}", file.path()?.to_string_lossy().to_string()); + // TODO when I'll be on a linux } Ok(()) } -async fn extract_zip(archive_path: PathBuf, extract_path: PathBuf) -> Result<()> { +async fn extract_zip(archive_path: PathBuf, extract_path: PathBuf, log_channel: mpsc::Sender) -> Result<()> { // TODO add log_channel to send progression to user let file = File::open(archive_path.clone()).await?; let mut reader = ZipFileReader::with_tokio(file).await?; + let total = reader.file().entries().len(); for index in 0..reader.file().entries().len() { if let Some(entry) = reader.file().entries().get(index) { let entry = entry.entry(); - // println!("{}", entry.filename().as_str()?); let filename = entry.filename().as_str()?; let path = extract_path.join(filename); + log_channel.send(ProgressMessage { p_type: "extract".to_string(), current: index + 1, total: total }).await?; if entry.dir()? { - if !path.exists() { - fs::create_dir_all(path).await?; + if path.exists() { + fs::remove_dir_all(path.clone()).await?; // clear folder before continue, avoid injection } + fs::create_dir_all(path).await?; // recreating the folder then } else { let mut entry_reader = reader.reader_with_entry(index).await?; if path.exists() { diff --git a/src-tauri/src/launcher/mod.rs b/src-tauri/src/launcher/mod.rs index 7b259b3..7efcaf7 100644 --- a/src-tauri/src/launcher/mod.rs +++ b/src-tauri/src/launcher/mod.rs @@ -79,7 +79,8 @@ impl<'a> MinecraftClient<'_> { self.opts.root_path.join("assets").join("objects"), self.opts.root_path.join("assets").join("indexes"), self.opts.root_path.join("runtime").join("download"), - ]; + self.opts.root_path.join("mods") + ]; let mut tasks = Vec::with_capacity(folders.len()); for folder in folders { if !folder.exists() { @@ -112,9 +113,14 @@ impl<'a> MinecraftClient<'_> { self.create_dirs().await?; let lib = &self.opts.root_path.join("libraries"); let asset = &self.opts.root_path.join("assets").join("objects"); + let modpack = &self.opts.root_path.join("modpack").join(chapter.title); + if !modpack.exists() { + fs::create_dir_all(modpack).await?; + } self.save_version_index().await?; chapter.java.platform.download_java(self.opts.root_path, &self.reqwest_client, self.opts.log_channel.clone()).await?; - chapter.java.platform.extract_java(self.opts.root_path).await?; + chapter.java.platform.extract_java(self.opts.root_path, self.opts.log_channel.clone()).await?; + chapter.modspack.download_mods(&self.reqwest_client, modpack.to_path_buf(), self.opts.log_channel.clone()).await?; self.download_libraries(lib).await?; self.download_assets(asset).await?; self.opts.log_channel.send(ProgressMessage { p_type: "completed".to_string(), current: 0, total: 0 }).await?;