feat: Add downloading libraries

This commit is contained in:
LynithDev 2024-03-22 22:00:27 +01:00
parent ad61294642
commit 45ab7048b6
4 changed files with 250 additions and 45 deletions

View file

@ -8,7 +8,7 @@ use uuid::Uuid;
use crate::utils::{file, http};
use super::{clients::vanilla::{VanillaClient, VanillaManifest}, JavaVersion};
use super::{clients::vanilla::{vanilla_manifest, VanillaClient, VanillaManifest}, JavaVersion};
#[macro_export]
macro_rules! create_manifest {
@ -60,8 +60,11 @@ pub fn get_game_client(handle: AppHandle, details: GameClientDetails) -> Box<dyn
#[async_trait]
pub trait GameClient: Send + Sync {
fn new(handle: AppHandle, details: GameClientDetails) -> Self where Self: Sized;
fn get_version_urls() -> HashMap<String, String> where Self: Sized;
fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> where Self: Sized;
async fn get_all_version_urls() -> Result<HashMap<String, String>, Box<dyn Error>> where Self: Sized;
async fn get_minecraft_manifest(version: String) -> Result<vanilla_manifest::MinecraftManifest, Box<dyn Error>> where Self: Sized;
// async fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> where Self: Sized;
fn get_handle(&self) -> &AppHandle;
fn get_details(&self) -> &GameClientDetails;

View file

@ -1,12 +1,16 @@
use std::{collections::HashMap, error::Error};
use async_trait::async_trait;
use tauri::AppHandle;
use serde_json::Value;
use tauri::{AppHandle, Manager};
use tokio::io::{stdout, AsyncWriteExt};
use crate::{create_client, create_manifest, game::client::{GameClient, GameClientDetails, GameClientType}};
use crate::{create_client, create_manifest, game::client::{GameClient, GameClientDetails, GameClientType}, utils::http};
create_manifest!(VanillaManifest {
// minecraft: vanilla_manifest::MinecraftManifest
});
create_manifest!(VanillaManifest {});
create_client!(VanillaClient {});
#[async_trait]
@ -18,12 +22,38 @@ impl GameClient for VanillaClient {
}
}
fn get_version_urls() -> HashMap<String, String> {
HashMap::new()
}
async fn get_all_version_urls() -> Result<HashMap<String, String>, Box<dyn Error>> {
const URL: &str = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json";
let request = http::create_client().get(URL).send().await?;
fn get_details_from_version(version: String) -> Result<GameClientDetails, Box<dyn Error>> {
Err("a".into())
let json = request.json::<Value>().await?;
let versions = json.get("versions").ok_or("No versions object")?
.as_array().ok_or("Invalid versions object")?;
let mut map = HashMap::new();
for version in versions {
let version = version.as_object().ok_or("Invalid version object")?;
let id = version.get("id").ok_or("No id object")?
.as_str().ok_or("Invalid id object")?;
let url = version.get("url").ok_or("No url object")?
.as_str().ok_or("Invalid url object")?;
map.insert(id.to_string(), url.to_string());
}
Ok(map)
}
async fn get_minecraft_manifest(version_url: String) -> Result<vanilla_manifest::MinecraftManifest, Box<dyn Error>> {
let request = http::create_client().get(version_url).send().await?;
match request.json::<vanilla_manifest::MinecraftManifest>().await {
Ok(manifest) => Ok(manifest),
Err(e) => Err(e.into())
}
}
fn get_handle(&self) -> &AppHandle {
@ -39,7 +69,13 @@ impl GameClient for VanillaClient {
}
async fn setup(&self) -> Result<(), Box<dyn Error>> {
// vanilla_impl::setup_libraries().await?;
let libraries_dir = self.get_handle().path().app_config_dir()?.join("libraries");
let versions = Self::get_all_version_urls().await?;
let version = versions.get(&self.get_details().version).ok_or("Version not found")?.to_owned();
let manifest = Self::get_minecraft_manifest(version).await?;
let libraries = vanilla_impl::setup_libraries(&manifest.libraries, &libraries_dir).await?;
println!("{:#?}", libraries);
// vanilla_impl::setup_natives().await?;
// vanilla_impl::setup_assets().await?;
Ok(())
@ -57,50 +93,59 @@ impl GameClient for VanillaClient {
}
pub mod vanilla_impl {
use std::error::Error;
use std::{error::Error, fs, path::PathBuf};
use serde_json::{Map, Value};
use tauri::AppHandle;
pub async fn setup_libraries(manifest: &Value) -> Result<Vec<String>, Box<dyn Error>> {
let libraries = match manifest.get("libraries") {
Some(libraries) => {
if libraries.is_array() {
libraries
} else {
return Err("Invalid libraries object".into())
}
},
None => return Ok(vec![])
};
use crate::utils::http::download_file;
let mut natives: Vec<&Map<String, Value>> = vec![];
for library in libraries.as_array().unwrap() {
let library: &Map<String, Value> = library.as_object().unwrap();
use super::vanilla_manifest::{self, Library};
pub async fn setup_libraries(libraries: &Vec<Library>, libraries_folder: &PathBuf) -> Result<Vec<String>, Box<dyn Error>> {
let mut natives_ret: Vec<&Library> = vec![];
let mut libraries_ret: Vec<String> = vec![];
for library in libraries {
// let library: &Map<String, Value> = library.as_object().unwrap();
if let Some(_) = library.get("natives") {
natives.push(library);
// if let Some(_) = library.get("natives") {
// natives_ret.push(library);
// continue;
// }
// let artifact = library.get("downloads").ok_or("No downloads object")?
// .as_object().ok_or("Invalid downloads object")?
// .get("artifact").ok_or("No artifact object")?
// .as_object().ok_or("Invalid artifact object")?;
// let path = artifact.get("path").ok_or("No path object")?
// .as_str().ok_or("Invalid path object")?;
// let url = artifact.get("url").ok_or("No url object")?
// .as_str().ok_or("Invalid url object")?;
if let Some(_) = library.natives {
natives_ret.push(library);
continue;
}
let name = library.get("name").ok_or("No name object")?
.as_str().ok_or("Invalid name object")?;
let artifact = library.get("downloads").ok_or("No downloads object")?
.as_object().ok_or("Invalid downloads object")?
.get("artifact").ok_or("No artifact object")?
.as_object().ok_or("Invalid artifact object")?;
let path = artifact.get("path").ok_or("No path object")?
.as_str().ok_or("Invalid path object")?;
let url = artifact.get("url").ok_or("No url object")?
.as_str().ok_or("Invalid url object")?;
let artifact = library.downloads.artifact.clone().ok_or("No artifact object")?;
let path = artifact.path;
let url = artifact.url;
// TODO: Add checks for rules + platform
let dest = libraries_folder.join(path);
fs::create_dir_all(dest.parent().ok_or("Couldn't get library parent")?)?;
if !dest.exists() {
download_file(url.as_str(), &dest).await?;
}
libraries_ret.push(dest.to_str().ok_or("Couldn't get library path")?.to_string());
}
Ok(vec![])
Ok(libraries_ret)
}
pub async fn setup_natives() -> Result<(), Box<dyn Error>> {
@ -110,4 +155,162 @@ pub mod vanilla_impl {
pub async fn setup_assets() -> Result<(), Box<dyn Error>> {
Ok(())
}
}
pub mod vanilla_manifest {
use serde::Deserialize;
use serde::Serialize;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MinecraftManifest {
pub asset_index: AssetIndex,
pub assets: String,
pub compliance_level: i64,
pub downloads: Downloads,
pub id: String,
pub java_version: JavaVersion,
pub libraries: Vec<Library>,
pub logging: Logging,
pub main_class: String,
pub minecraft_arguments: String,
pub minimum_launcher_version: i64,
pub release_time: String,
pub time: String,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssetIndex {
pub id: String,
pub sha1: String,
pub size: i64,
pub total_size: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Downloads {
pub client: Client,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Client {
pub sha1: String,
pub size: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JavaVersion {
pub component: String,
pub major_version: i64,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Library {
pub downloads: Downloads2,
pub name: String,
#[serde(default)]
pub rules: Vec<Rule>,
pub extract: Option<Extract>,
pub natives: Option<Natives>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Downloads2 {
pub artifact: Option<Artifact>,
pub classifiers: Option<Classifiers>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artifact {
pub path: String,
pub sha1: String,
pub size: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Classifiers {
#[serde(rename = "natives-windows-32")]
pub natives_windows_32: Option<NativeLibrary>,
#[serde(rename = "natives-windows-64")]
pub natives_windows_64: Option<NativeLibrary>,
#[serde(rename = "natives-osx")]
pub natives_osx: Option<NativeLibrary>,
#[serde(rename = "natives-linux")]
pub natives_linux: Option<NativeLibrary>,
#[serde(rename = "natives-windows")]
pub natives_windows: Option<NativeLibrary>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NativeLibrary {
pub path: String,
pub sha1: String,
pub size: i64,
pub url: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Rule {
pub action: String,
pub os: Option<Os>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Os {
pub name: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Extract {
pub exclude: Vec<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Natives {
pub windows: String,
pub linux: Option<String>,
pub osx: Option<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Logging {
pub client: Client2,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Client2 {
pub argument: String,
pub file: File,
#[serde(rename = "type")]
pub type_field: String,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct File {
pub id: String,
pub sha1: String,
pub size: i64,
pub url: String,
}
}

View file

@ -1,4 +1,4 @@
import { type Accessor, type JSX, createEffect, createSignal, onMount } from 'solid-js';
import { type Accessor, type JSX, createEffect, createSignal } from 'solid-js';
import Popup from './Popup';
type ContextMenuProps = {

View file

@ -8,7 +8,7 @@ import {
SearchMdIcon,
} from '@untitled-theme/icons-solid';
import type { Accessor, Setter } from 'solid-js';
import { For, createSignal, untrack } from 'solid-js';
import { For, createSignal } from 'solid-js';
import { DragDropProvider, DragDropSensors, SortableProvider, closestCenter, createSortable } from '@thisbeyond/solid-dnd';
import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd';
import { useNavigate } from '@solidjs/router';
@ -18,7 +18,6 @@ import Button from '../components/base/Button';
import Tag from '../components/base/Tag';
import TextField from '../components/base/TextField';
import defaultCover from '../../assets/images/default_instance_cover.jpg';
import Popup from '../components/overlay/Popup';
import ContextMenu from '../components/overlay/ContextMenu';
// TODO: Replace this into it's own component