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 anyhow::{Result, bail, anyhow};
@ -11,7 +13,9 @@ use tokio_stream::StreamExt;
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)]
@ -78,7 +82,13 @@ impl AltarikManifest {
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(())
}
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 {
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,
@ -161,7 +165,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");
extract_zip(archive_path, extract_path, log_channel).await?;
extract_zip(&archive_path, &extract_path).await?;
Ok(())
}
@ -171,7 +175,7 @@ impl JavaPlatform {
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() {
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"))?;
@ -187,12 +191,15 @@ impl ModsPack {
.write(true)
.create_new(true)
.append(true)
.open(filepath)
.open(filepath.clone())
.await?;
while let Some(chunk) = res.chunk().await? {
file.write_all(&chunk).await?;
}
} else {
println!("Mod {} already downloaded", sha1);
}
extract_zip(&filepath, &root_dir).await?;
}
Ok(())
}
@ -202,11 +209,11 @@ impl ModsPack {
let mut hasher = Sha1::new();
let mut file = File::open(filepath).await?;
let mut content = Vec::new();
file.read(&mut content).await?;
file.read_to_end(&mut content).await?;
hasher.update(content);
let hash = hasher.finalize();
// 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);
fs::remove_file(filepath).await?;
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 mut archive = Archive::new(file);
let mut entries = archive.entries()?;
@ -234,17 +241,15 @@ async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf, log_channel
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
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();
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::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 anyhow::{Result, bail};
use anyhow::{Result, bail, anyhow};
use reqwest::{Client, StatusCode};
use serde::{Serialize, Deserialize};
use tokio::{fs::{self, File}, io::{AsyncWriteExt, AsyncSeekExt}, sync::mpsc};
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")]
@ -46,13 +46,15 @@ pub struct ClientOptions<'a> {
pub struct MinecraftClient<'a> {
opts: &'a ClientOptions<'a>,
details: VersionDetail,
custom_details: Option<CustomVersionManifest>,
assets: AssetsManifest,
reqwest_client: Client,
chapter: Chapter,
}
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 manifest = Self::load_manifest(&reqwest_client, &opts).await?;
let details = manifest.0;
@ -61,7 +63,9 @@ impl<'a> MinecraftClient<'_> {
opts,
reqwest_client,
details,
custom_details: None,
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
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);
let modpack = &self.opts.root_path.join("modpack").join(self.chapter.title.clone());
if !modpack.exists() {
fs::create_dir_all(modpack).await?;
}
self.clear_folder().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, self.opts.log_channel.clone()).await?;
chapter.modspack.download_mods(&self.reqwest_client, modpack.to_path_buf(), 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?;
self.chapter.java.platform.extract_java(self.opts.root_path).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_custom_libraries(lib).await?;
self.download_assets(asset).await?;
self.opts.log_channel.send(ProgressMessage { p_type: "completed".to_string(), current: 0, total: 0 }).await?;
Ok(())
@ -131,6 +138,7 @@ impl<'a> MinecraftClient<'_> {
self.filter_non_necessary_librairies();
let total = self.details.libraries.len();
for (progress, i) in self.details.libraries.iter().enumerate() {
let p = i.downloads.artifact.path.clone();
let mut splited = p.split("/").collect::<Vec<&str>>();
let filename = splited.pop().ok_or(anyhow::anyhow!("Invalid filename"))?; // remove last element
@ -174,6 +182,24 @@ impl<'a> MinecraftClient<'_> {
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> {
if (&file_path).exists() {
let f = fs::File::open(&file_path).await;
@ -188,7 +214,7 @@ impl<'a> MinecraftClient<'_> {
/// Filter non necessary librairies for the current OS
fn filter_non_necessary_librairies(&mut self) {
self.details.libraries.retain(|e| { Self::should_use_library(e) });
self.details.libraries.retain(|e| { Self::should_use_library(e) });
}
async fn download_assets(&mut self, object_folder: &PathBuf) -> Result<()> {
@ -216,9 +242,9 @@ impl<'a> MinecraftClient<'_> {
.await?;
file.write_all(&received).await?;
println!("{} downloaded", value.hash);
} else {
println!("{} already downloaded", value.hash);
}
} // else {
// println!("{} already downloaded", value.hash);
// }
self.opts.log_channel.send( ProgressMessage { p_type: "assets".to_string(), current: progress + 1, total }).await?;
}
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> {
let client = MinecraftClient::new(&opts).await;
let client = MinecraftClient::new(&opts, chapter).await;
let res = match client {
Ok(mut client) => {
match client.download_requirements(chapter).await {
match client.download_requirements().await {
Ok(_) => {
Ok("Content downloaded".to_string())
},