refactor: Change cluster file structure + use ClusterWithManifest over singular Cluster and Manifest types
This commit is contained in:
parent
e12045697e
commit
025ffa03e5
10 changed files with 124 additions and 135 deletions
|
|
@ -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 }))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -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
6
src/types/game.d.ts
vendored
|
|
@ -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
3
src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
declare namespace globalThis {
|
||||
export type WithIndex<T> = T & { index: number }
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue