Moved chapter to MinecraftClient struct

Creating of a new struct for fabric manifest because library resolution is different than minecraft official
This commit is contained in:
Quentin Legot 2023-10-07 01:33:26 +02:00
parent 2c84ebb87e
commit 5b8effd7c4
4 changed files with 94 additions and 35 deletions

View File

@ -0,0 +1,28 @@
//! module for fabric version detail manifest
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct CustomVersionManifest {
#[serde(rename(serialize = "inheritsFrom", deserialize = "inheritsFrom"))]
pub inherits_from: String,
#[serde(rename(serialize = "mainClass", deserialize = "mainClass"))]
pub main_class: String,
pub libraries: Vec<CustomLibrary>,
pub arguments: CustomArguments,
pub id: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
pub t_type: String,
}
#[derive(Serialize, Deserialize)]
pub struct CustomLibrary {
pub name: String,
pub url: String,
}
#[derive(Serialize, Deserialize)]
pub struct CustomArguments {
jvm: Vec<String>,
game: Vec<String>,
}

View File

@ -1,3 +1,5 @@
pub mod custom_version_manifest;
use std::path::{PathBuf, Path}; use std::path::{PathBuf, Path};
use anyhow::{Result, bail, anyhow}; use anyhow::{Result, bail, anyhow};
@ -11,7 +13,9 @@ use tokio_stream::StreamExt;
use crate::launcher::ProgressMessage; use crate::launcher::ProgressMessage;
use super::{ACTUAL_OS, manifest::OSName}; use self::custom_version_manifest::CustomVersionManifest;
use super::{ACTUAL_OS, manifest::{OSName, VersionDetail}};
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
@ -78,7 +82,13 @@ impl AltarikManifest {
impl Chapter { impl Chapter {
pub async fn get_custom_version_detail_manifest(&self, version_dir: &PathBuf) -> Result<CustomVersionManifest> {
let filepath = version_dir.join(self.custom_version.to_string()).join(format!("{}.json", self.custom_version.to_string()));
let mut file = File::open(filepath).await?;
let mut content = String::new();
file.read_to_string(&mut content).await?;
Ok(serde_json::from_str(&content)?)
}
} }
@ -143,17 +153,11 @@ impl JavaPlatform {
Ok(()) Ok(())
} }
pub async fn extract_java(&self, root_path: &Path, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> { pub async fn extract_java(&self, root_path: &Path) -> Result<()> {
let (url, extension) = match ACTUAL_OS { let (url, extension) = match ACTUAL_OS {
OSName::Linux => { OSName::Linux => (self.linux.clone(), "tar.gz"),
(self.linux.clone(), "tar.gz") OSName::Windows => (self.win32.clone(), "zip"),
}, _ => bail!("Your current is not supported")
OSName::Windows => {
(self.win32.clone(), "zip")
},
_ => {
bail!("Your current is not supported")
}
}; };
let url = match url { let url = match url {
Some(url) => url, Some(url) => url,
@ -161,7 +165,7 @@ impl JavaPlatform {
}; };
let archive_path = root_path.join("runtime").join("download").join(format!("{}.{}", url.x64.name, extension)); let archive_path = root_path.join("runtime").join("download").join(format!("{}.{}", url.x64.name, extension));
let extract_path = root_path.join("runtime"); let extract_path = root_path.join("runtime");
extract_zip(archive_path, extract_path, log_channel).await?; extract_zip(&archive_path, &extract_path).await?;
Ok(()) Ok(())
} }
@ -171,7 +175,7 @@ impl JavaPlatform {
impl ModsPack { impl ModsPack {
pub async fn download_mods(&self, reqwest: &Client, modpack_dir: PathBuf, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> { pub async fn download_mods(&self, reqwest: &Client, modpack_dir: &PathBuf, root_dir: &PathBuf, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> {
for index in 0..self.mods.len() { for index in 0..self.mods.len() {
log_channel.send(ProgressMessage { p_type: "mods".to_string(), current: index, total: self.mods.len() }).await?; 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 mod_url = self.mods.get(index).ok_or(anyhow!("Cannot get mod download link"))?;
@ -187,12 +191,15 @@ impl ModsPack {
.write(true) .write(true)
.create_new(true) .create_new(true)
.append(true) .append(true)
.open(filepath) .open(filepath.clone())
.await?; .await?;
while let Some(chunk) = res.chunk().await? { while let Some(chunk) = res.chunk().await? {
file.write_all(&chunk).await?; file.write_all(&chunk).await?;
} }
} else {
println!("Mod {} already downloaded", sha1);
} }
extract_zip(&filepath, &root_dir).await?;
} }
Ok(()) Ok(())
} }
@ -202,11 +209,11 @@ impl ModsPack {
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
let mut file = File::open(filepath).await?; let mut file = File::open(filepath).await?;
let mut content = Vec::new(); let mut content = Vec::new();
file.read(&mut content).await?; file.read_to_end(&mut content).await?;
hasher.update(content); hasher.update(content);
let hash = hasher.finalize(); let hash = hasher.finalize();
// let b16 = base16ct::upper::encode_string(hash); // let b16 = base16ct::upper::encode_string(hash);
if &&format!("{:x}", &hash.clone()) != mod_sha1 { if format!("{:x}", hash.clone()) != mod_sha1.to_lowercase() {
println!("Correct: {:?}, current: {:X}", mod_sha1, hash); println!("Correct: {:?}, current: {:X}", mod_sha1, hash);
fs::remove_file(filepath).await?; fs::remove_file(filepath).await?;
Ok(true) Ok(true)
@ -221,7 +228,7 @@ impl ModsPack {
} }
async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> { async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf) -> Result<()> {
let file = File::open(archive_path.clone()).await?; let file = File::open(archive_path.clone()).await?;
let mut archive = Archive::new(file); let mut archive = Archive::new(file);
let mut entries = archive.entries()?; let mut entries = archive.entries()?;
@ -234,17 +241,15 @@ async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf, log_channel
Ok(()) Ok(())
} }
async fn extract_zip(archive_path: PathBuf, extract_path: PathBuf, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> { async fn extract_zip(archive_path: &PathBuf, extract_path: &PathBuf) -> Result<()> {
// TODO add log_channel to send progression to user // TODO add log_channel to send progression to user
let file = File::open(archive_path.clone()).await?; let file = File::open(archive_path.clone()).await?;
let mut reader = ZipFileReader::with_tokio(file).await?; let mut reader = ZipFileReader::with_tokio(file).await?;
let total = reader.file().entries().len();
for index in 0..reader.file().entries().len() { for index in 0..reader.file().entries().len() {
if let Some(entry) = reader.file().entries().get(index) { if let Some(entry) = reader.file().entries().get(index) {
let entry = entry.entry(); let entry = entry.entry();
let filename = entry.filename().as_str()?; let filename = entry.filename().as_str()?;
let path = extract_path.join(filename); 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 entry.dir()? {
if path.exists() { if path.exists() {
fs::remove_dir_all(path.clone()).await?; // clear folder before continue, avoid injection fs::remove_dir_all(path.clone()).await?; // clear folder before continue, avoid injection

View File

@ -3,14 +3,14 @@ pub mod altarik;
use std::path::{Path, self, PathBuf}; use std::path::{Path, self, PathBuf};
use anyhow::{Result, bail}; use anyhow::{Result, bail, anyhow};
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use tokio::{fs::{self, File}, io::{AsyncWriteExt, AsyncSeekExt}, sync::mpsc}; use tokio::{fs::{self, File}, io::{AsyncWriteExt, AsyncSeekExt}, sync::mpsc};
use crate::authentification::GameProfile; use crate::authentification::GameProfile;
use self::{manifest::{VersionDetail, get_version_manifest, get_version_from_manifest, get_version_detail, Library, OSName, get_version_assets, AssetsManifest}, altarik::Chapter}; use self::{manifest::{VersionDetail, get_version_manifest, get_version_from_manifest, get_version_detail, Library, OSName, get_version_assets, AssetsManifest}, altarik::{Chapter, custom_version_manifest::CustomVersionManifest}};
#[cfg(target_os="windows")] #[cfg(target_os="windows")]
@ -46,13 +46,15 @@ pub struct ClientOptions<'a> {
pub struct MinecraftClient<'a> { pub struct MinecraftClient<'a> {
opts: &'a ClientOptions<'a>, opts: &'a ClientOptions<'a>,
details: VersionDetail, details: VersionDetail,
custom_details: Option<CustomVersionManifest>,
assets: AssetsManifest, assets: AssetsManifest,
reqwest_client: Client, reqwest_client: Client,
chapter: Chapter,
} }
impl<'a> MinecraftClient<'_> { impl<'a> MinecraftClient<'_> {
pub async fn new(opts: &'a ClientOptions<'a>) -> Result<MinecraftClient<'a>> { pub async fn new(opts: &'a ClientOptions<'a>, chapter: Chapter) -> Result<MinecraftClient<'a>> {
let reqwest_client = Client::new(); let reqwest_client = Client::new();
let manifest = Self::load_manifest(&reqwest_client, &opts).await?; let manifest = Self::load_manifest(&reqwest_client, &opts).await?;
let details = manifest.0; let details = manifest.0;
@ -61,7 +63,9 @@ impl<'a> MinecraftClient<'_> {
opts, opts,
reqwest_client, reqwest_client,
details, details,
custom_details: None,
assets, assets,
chapter
}) })
} }
@ -108,20 +112,23 @@ impl<'a> MinecraftClient<'_> {
} }
} }
pub async fn download_requirements(&mut self, chapter: Chapter) -> Result<()> { pub async fn download_requirements(&mut self) -> Result<()> {
// create root folder if it doesn't exist // create root folder if it doesn't exist
self.create_dirs().await?; self.create_dirs().await?;
let lib = &self.opts.root_path.join("libraries"); let lib = &self.opts.root_path.join("libraries");
let asset = &self.opts.root_path.join("assets").join("objects"); let asset = &self.opts.root_path.join("assets").join("objects");
let modpack = &self.opts.root_path.join("modpack").join(chapter.title); let modpack = &self.opts.root_path.join("modpack").join(self.chapter.title.clone());
if !modpack.exists() { if !modpack.exists() {
fs::create_dir_all(modpack).await?; fs::create_dir_all(modpack).await?;
} }
self.clear_folder().await?;
self.save_version_index().await?; self.save_version_index().await?;
chapter.java.platform.download_java(self.opts.root_path, &self.reqwest_client, self.opts.log_channel.clone()).await?; self.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, self.opts.log_channel.clone()).await?; self.chapter.java.platform.extract_java(self.opts.root_path).await?;
chapter.modspack.download_mods(&self.reqwest_client, modpack.to_path_buf(), self.opts.log_channel.clone()).await?; self.chapter.modspack.download_mods(&self.reqwest_client, modpack, &self.opts.root_path.to_path_buf(), self.opts.log_channel.clone()).await?;
self.custom_details = Some(self.chapter.get_custom_version_detail_manifest(&self.opts.root_path.join("versions")).await?);
self.download_libraries(lib).await?; self.download_libraries(lib).await?;
self.download_custom_libraries(lib).await?;
self.download_assets(asset).await?; self.download_assets(asset).await?;
self.opts.log_channel.send(ProgressMessage { p_type: "completed".to_string(), current: 0, total: 0 }).await?; self.opts.log_channel.send(ProgressMessage { p_type: "completed".to_string(), current: 0, total: 0 }).await?;
Ok(()) Ok(())
@ -131,6 +138,7 @@ impl<'a> MinecraftClient<'_> {
self.filter_non_necessary_librairies(); self.filter_non_necessary_librairies();
let total = self.details.libraries.len(); let total = self.details.libraries.len();
for (progress, i) in self.details.libraries.iter().enumerate() { for (progress, i) in self.details.libraries.iter().enumerate() {
let p = i.downloads.artifact.path.clone(); let p = i.downloads.artifact.path.clone();
let mut splited = p.split("/").collect::<Vec<&str>>(); let mut splited = p.split("/").collect::<Vec<&str>>();
let filename = splited.pop().ok_or(anyhow::anyhow!("Invalid filename"))?; // remove last element let filename = splited.pop().ok_or(anyhow::anyhow!("Invalid filename"))?; // remove last element
@ -174,6 +182,24 @@ impl<'a> MinecraftClient<'_> {
Ok(()) Ok(())
} }
async fn download_custom_libraries(&self, lib_dir: &PathBuf) -> Result<()> {
if let Some(custom) = &self.custom_details {
for i in &custom.libraries {
}
}
Ok(())
}
/// delete and recreate some folder, in particular mods and version folder
async fn clear_folder(&self) -> Result<()> {
for i in [self.opts.root_path.join("mods"), self.opts.root_path.join("versions")] {
fs::remove_dir_all(&i).await?;
fs::create_dir_all(&i).await?;
}
Ok(())
}
async fn select_file_option(file_path: &PathBuf, expected_size: u64) -> Result<File> { async fn select_file_option(file_path: &PathBuf, expected_size: u64) -> Result<File> {
if (&file_path).exists() { if (&file_path).exists() {
let f = fs::File::open(&file_path).await; let f = fs::File::open(&file_path).await;
@ -216,9 +242,9 @@ impl<'a> MinecraftClient<'_> {
.await?; .await?;
file.write_all(&received).await?; file.write_all(&received).await?;
println!("{} downloaded", value.hash); println!("{} downloaded", value.hash);
} else { } // else {
println!("{} already downloaded", value.hash); // println!("{} already downloaded", value.hash);
} // }
self.opts.log_channel.send( ProgressMessage { p_type: "assets".to_string(), current: progress + 1, total }).await?; self.opts.log_channel.send( ProgressMessage { p_type: "assets".to_string(), current: progress + 1, total }).await?;
} }
Ok(()) Ok(())

View File

@ -114,10 +114,10 @@ async fn download(selected_chapter: usize, app: tauri::AppHandle, state: tauri::
async fn download_libraries(opts: ClientOptions<'_>, chapter: Chapter) -> Result<String, String> { async fn download_libraries(opts: ClientOptions<'_>, chapter: Chapter) -> Result<String, String> {
let client = MinecraftClient::new(&opts).await; let client = MinecraftClient::new(&opts, chapter).await;
let res = match client { let res = match client {
Ok(mut client) => { Ok(mut client) => {
match client.download_requirements(chapter).await { match client.download_requirements().await {
Ok(_) => { Ok(_) => {
Ok("Content downloaded".to_string()) Ok("Content downloaded".to_string())
}, },