refactor: Change cluster file structure + use ClusterWithManifest over singular Cluster and Manifest types

This commit is contained in:
LynithDev 2024-04-01 15:30:00 +02:00
parent e12045697e
commit 025ffa03e5
10 changed files with 124 additions and 135 deletions

View file

@ -2,7 +2,7 @@ use std::{collections::HashMap, fs, path::PathBuf};
use anyhow::anyhow;
use chrono::Local;
use serde::{de::DeserializeOwned, Serialize};
use serde::Serialize;
use uuid::Uuid;
use crate::{auth::Account, utils::dirs};
@ -18,57 +18,42 @@ pub enum ClientManagerError {
ManifestNotFound,
}
#[derive(Debug, Clone, Serialize)]
pub struct ClusterWithManifest {
pub cluster: Cluster,
pub manifest: Manifest
}
pub struct ClientManager {
clusters: HashMap<Uuid, Cluster>,
manifests: HashMap<Uuid, Manifest>,
clusters: HashMap<Uuid, ClusterWithManifest>,
}
impl ClientManager {
pub fn new() -> crate::Result<Self> {
Ok(Self {
clusters: load_and_serialize::<Cluster>(&dirs::clusters_dir()?)?,
manifests: load_and_serialize::<Manifest>(&dirs::manifests_dir()?)?,
})
clusters: load_and_serialize()?
})
}
pub fn get_impl_uuid<'a>(&'a self, uuid: Uuid) -> crate::Result<Box<dyn ClientTrait<'a> + 'a>> {
let cluster = self.get_cluster(uuid)?;
let manifest = self.get_manifest(uuid)?;
let ClusterWithManifest { cluster, manifest } = self.get_cluster(uuid)?;
Ok(clients::get_impl(&cluster.client, &cluster, &manifest))
Ok(clients::get_impl(&cluster.client, cluster, manifest))
}
pub fn get_clusters(&self) -> Vec<&Cluster> {
pub fn get_clusters(&self) -> Vec<&ClusterWithManifest> {
self.clusters.values().collect()
}
pub fn get_clusters_owned(&self) -> Vec<Cluster> {
pub fn get_clusters_owned(&self) -> Vec<ClusterWithManifest> {
self.clusters.values().cloned().collect()
}
pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&Cluster> {
self.clusters
.get(&uuid)
.ok_or(ClientManagerError::ClusterNotFound.into())
}
pub fn get_cluster_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Cluster> {
self.clusters
.get_mut(&uuid)
.ok_or(ClientManagerError::ClusterNotFound.into())
}
pub fn get_manifest(&self, uuid: Uuid) -> crate::Result<&Manifest> {
self.manifests
.get(&uuid)
.ok_or(ClientManagerError::ManifestNotFound.into())
}
pub fn get_manifest_mut(&mut self, uuid: Uuid) -> crate::Result<&mut Manifest> {
self.manifests
.get_mut(&uuid)
.ok_or(ClientManagerError::ManifestNotFound.into())
}
pub fn get_cluster(&self, uuid: Uuid) -> crate::Result<&ClusterWithManifest> {
self.clusters
.get(&uuid)
.ok_or(ClientManagerError::ClusterNotFound.into())
}
}
impl ClientManager {
@ -117,6 +102,7 @@ impl ClientManager {
.url;
let uuid = Uuid::new_v4();
let dir = &dirs::cluster_dir(uuid.to_string())?;
// Save the manifest
// TODO: Is this correct?
@ -127,10 +113,9 @@ impl ClientManager {
};
save(
&dirs::manifests_dir()?.join(format!("{}.json", uuid)),
&dir.join("manifest.json"),
&manifest,
)?;
self.manifests.insert(uuid, manifest);
// Save the cluster
let cluster = Cluster {
@ -143,10 +128,11 @@ impl ClientManager {
};
save(
&dirs::clusters_dir()?.join(format!("{}.json", uuid)),
&dir.join("cluster.json"),
&cluster,
)?;
self.clusters.insert(uuid, cluster);
self.clusters.insert(uuid, ClusterWithManifest { cluster, manifest });
Ok(uuid)
}
@ -168,51 +154,40 @@ where
Ok(())
}
fn load_and_serialize<T>(dir: &PathBuf) -> crate::Result<HashMap<Uuid, T>>
where
T: DeserializeOwned,
{
let mut result = HashMap::<Uuid, T>::new();
fn load_and_serialize() -> crate::Result<HashMap<Uuid, ClusterWithManifest>> {
let mut result = HashMap::<Uuid, ClusterWithManifest>::new();
if !dir.exists() {
fs::create_dir_all(dir)?;
return Ok(result);
}
let dir = dirs::clusters_dir()?;
if !dir.exists() {
fs::create_dir_all(&dir)?;
return Ok(result);
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
for file in fs::read_dir(dir)? {
let file = file?;
let file_name = file.file_name();
let file_name = file_name
.to_str()
.ok_or(anyhow!("Couldn't convert OsStr to str"))?
.replace(".json", "");
let uuid = match Uuid::parse_str(&file_name) {
Ok(uuid) => uuid,
Err(_) => {
eprintln!("Couldn't parse file name as UUID: '{:?}'", file_name);
continue;
}
};
let path = file.path();
let content = match fs::read_to_string(&path) {
Ok(content) => content,
Err(_) => {
eprintln!("Couldn't read file: '{:?}'", path);
continue;
}
};
let parsed = match serde_json::from_str::<T>(&content) {
Ok(parsed) => parsed,
Err(_) => {
eprintln!("Couldn't parse file '{}' content as JSON", path.display());
continue;
}
};
result.insert(uuid, parsed);
}
if path.is_dir() {
match get_entry(&path) {
Ok((uuid, cluster)) => {
result.insert(uuid, cluster);
}
Err(e) => {
eprintln!("Failed to load cluster: {}", e);
}
}
}
}
Ok(result)
}
fn get_entry(path: &PathBuf) -> crate::Result<(Uuid, ClusterWithManifest)> {
let manifest_path = path.join("manifest.json");
let cluster_path = path.join("cluster.json");
let manifest: Manifest = serde_json::from_str(&fs::read_to_string(manifest_path)?)?;
let cluster: Cluster = serde_json::from_str(&fs::read_to_string(cluster_path)?)?;
Ok((cluster.id, ClusterWithManifest { cluster, manifest }))
}

View file

@ -1,7 +1,7 @@
use crate::{
constants::{self, MINECRAFT_VERSIONS_MANIFEST},
create_game_client,
game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{Library, MinecraftManifest, MinecraftVersion, RuleListExt}},
game::{client::{ClientTrait, Cluster, Manifest, SetupInfo}, minecraft::{AssetIndexFile, Library, MinecraftManifest, MinecraftVersion, RuleListExt}},
impl_game_client,
utils::{dirs, file, http}
};
@ -37,7 +37,7 @@ impl<'a> ClientTrait<'a> for VanillaClient<'a> {
libraries.push_str(constants::LIBRARY_SPLITTER);
libraries.push_str(client_path.to_str().unwrap());
let game_dir = dirs::game_dir()?.join(&self.cluster.id.to_string());
let game_dir = dirs::cluster_dir(self.cluster.id.to_string())?.join("game");
fs::create_dir_all(&game_dir)?;
Ok(SetupInfo {
@ -125,11 +125,35 @@ pub async fn install_natives(natives: Vec<&Library>) -> crate::Result<()> {
Ok(())
}
pub async fn install_assets() -> crate::Result<()> {
pub async fn install_assets(manifest: &MinecraftManifest) -> crate::Result<()> {
let assets = dirs::assets_dir()?;
let objects = assets.join("objects");
let indexes = assets.join("indexes");
let index = indexes.join(format!("{}.json", manifest.asset_index.id));
fs::create_dir_all(&objects)?;
fs::create_dir_all(&indexes)?;
if !index.exists() {
let artifact = &manifest.asset_index;
http::download_file_sha1_check(&artifact.url, &index, &artifact.sha1).await?;
}
let contents = serde_json::from_str::<AssetIndexFile>(&fs::read_to_string(&index)?)?;
for (hash, asset) in contents.objects {
let short = &hash[..2];
let file = objects.join(&short).join(&hash);
fs::create_dir_all(file.parent().ok_or(anyhow!("Couldn't get asset parent"))?)?;
if !file.exists() {
let url = format!("https://resources.download.minecraft.net/{}/{}", &short, hash);
http::download_file_sha1_check(&url, &file, &asset.hash).await?;
}
}
Ok(())
}
#[derive(Clone, Serialize, Deserialize)]
struct VersionList {
versions: Vec<MinecraftVersion>,

View file

@ -266,6 +266,17 @@ pub struct File {
pub url: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssetIndexFile {
pub objects: HashMap<String, AssetObject>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AssetObject {
pub hash: String,
pub size: u64,
}
fn string_or_seq<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,

View file

@ -14,6 +14,10 @@ pub fn app_config_dir() -> crate::Result<PathBuf> {
.ok_or(DirectoryError::ConfigDir.into())
}
pub fn assets_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("assets"))
}
pub fn libraries_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("libraries"))
}
@ -30,14 +34,10 @@ pub fn clusters_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("clusters"))
}
pub fn manifests_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("manifests"))
pub fn cluster_dir(uuid: String) -> crate::Result<PathBuf> {
Ok(clusters_dir()?.join(uuid))
}
pub fn clients_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("clients"))
}
pub fn game_dir() -> crate::Result<PathBuf> {
Ok(app_config_dir()?.join("games"))
}

View file

@ -1,4 +1,4 @@
use onelauncher::{game::{client::{Cluster, LaunchCallbacks, Manifest}, clients::ClientType}, AppState};
use onelauncher::{game::{client::LaunchCallbacks, client_manager::ClusterWithManifest, clients::ClientType}, AppState};
use tauri::{Manager, State};
use tokio::sync::Mutex;
use uuid::Uuid;
@ -22,7 +22,7 @@ pub async fn create_cluster(
#[tauri::command]
pub async fn get_clusters(
state: State<'_, Mutex<AppState>>,
) -> Result<Vec<Cluster>, String> {
) -> Result<Vec<ClusterWithManifest>, String> {
let manager = &mut state.lock().await.clients;
Ok(manager.get_clusters_owned())
}
@ -31,10 +31,10 @@ pub async fn get_clusters(
pub async fn get_cluster(
state: State<'_, Mutex<AppState>>,
uuid: Uuid,
) -> Result<Cluster, String> {
) -> Result<ClusterWithManifest, String> {
let manager = &mut state.lock().await.clients;
let instance = manager.get_cluster(uuid)?;
Ok(instance.clone())
let cluster = manager.get_cluster(uuid)?;
Ok(cluster.clone())
}
#[tauri::command]
@ -66,13 +66,3 @@ pub async fn launch_cluster(
let exit_code = manager.launch_cluster(uuid, callbacks).await?;
Ok(exit_code)
}
#[tauri::command]
pub async fn get_manifest(
state: State<'_, Mutex<AppState>>,
uuid: Uuid,
) -> Result<Manifest, String> {
let manager = &mut state.lock().await.clients;
let manifest = manager.get_manifest(uuid)?;
Ok(manifest.clone())
}

View file

@ -17,7 +17,6 @@ pub fn init() -> TauriPlugin<tauri::Wry> {
game::create_cluster,
game::get_clusters,
game::get_cluster,
game::get_manifest,
game::launch_cluster,
refresh_client_manager,
])

View file

@ -1,36 +1,23 @@
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
export async function getCluster(uuid: string): Promise<Core.Cluster> {
return await invoke<Core.Cluster>(
export async function getCluster(uuid: string): Promise<Core.ClusterWithManifest> {
return await invoke<Core.ClusterWithManifest>(
'plugin:onelauncher|get_cluster',
{ uuid },
);
}
export async function getManifest(uuid: string): Promise<Core.Manifest> {
return await invoke<Core.Manifest>(
'plugin:onelauncher|get_manifest',
{ uuid },
);
}
export async function getClustersWithManifests(): Promise<Core.ClusterWithManifest[]> {
export async function getClustersGrouped(): Promise<Map<string, WithIndex<Core.ClusterWithManifest>[]>> {
const clusters = await getClusters();
return Promise.all(clusters.map(async cluster => ({
cluster,
manifest: await getManifest(cluster.id),
})));
}
export async function getClustersGrouped(): Promise<Map<string, Core.ClusterWithManifest[]>> {
const clusters = await getClustersWithManifests();
const map = new Map<string, Core.ClusterWithManifest[]>();
const map = new Map<string, WithIndex<Core.ClusterWithManifest>[]>();
clusters.forEach((cluster, index) => {
const groupName = cluster.cluster.group || 'Unnamed';
const value = { ...cluster, index };
const value: WithIndex<Core.ClusterWithManifest> = {
...cluster,
index,
};
if (map.has(groupName))
map.get(groupName)!.push(value);
@ -41,8 +28,8 @@ export async function getClustersGrouped(): Promise<Map<string, Core.ClusterWith
return map;
}
export async function getClusters(): Promise<Core.Cluster[]> {
return await invoke<Core.Cluster[]>('plugin:onelauncher|get_clusters');
export async function getClusters(): Promise<Core.ClusterWithManifest[]> {
return await invoke<Core.ClusterWithManifest[]>('plugin:onelauncher|get_clusters');
}
export async function createCluster(cluster: Omit<Core.Cluster, 'id' | 'createdAt'> & { version: string }): Promise<void> {

6
src/types/game.d.ts vendored
View file

@ -1,7 +1,7 @@
declare namespace Core {
export interface ClusterWithManifest {
cluster: Cluster,
manifest: Manifest,
export type ClusterWithManifest<T extends keyof ClientType = keyof ClientType> = {
cluster: Cluster<T>,
manifest: Manifest
}
export interface Manifest {

3
src/types/global.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
declare namespace globalThis {
export type WithIndex<T> = T & { index: number }
}

View file

@ -75,8 +75,8 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
navigate(`/clusters/?id=${props.id}`);
}
function launch() {
const exit_code = launchCluster(props.id, {
async function launch() {
const exit_code = await launchCluster(props.id, {
on_launch: pid => console.log('Launched with PID:', pid),
on_stdout: line => console.log('STDOUT:', line),
on_stderr: line => console.log('STDERR:', line),