229 lines
6.5 KiB
Rust
229 lines
6.5 KiB
Rust
use std::{collections::HashMap, fs, path::PathBuf};
|
|
|
|
use anyhow::anyhow;
|
|
use async_trait::async_trait;
|
|
use chrono::{DateTime, Local};
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
use crate::{utils::dirs, PolyResult};
|
|
|
|
use super::{minecraft::{MinecraftManifest, ReleaseType}, vanilla::{self, VanillaClientProps}};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct Version {
|
|
id: String,
|
|
url: String,
|
|
#[serde(default)]
|
|
release_type: ReleaseType,
|
|
#[serde(default)]
|
|
release_time: chrono::DateTime<chrono::Utc>
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct Manifest {
|
|
pub id: Uuid,
|
|
pub manifest: MinecraftManifest
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Instance {
|
|
pub id: Uuid,
|
|
pub created_at: DateTime<Local>,
|
|
pub name: String,
|
|
pub cover: Option<String>,
|
|
pub group: Option<Uuid>,
|
|
pub client: ClientType,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type")]
|
|
pub enum ClientType {
|
|
Vanilla(VanillaClientProps)
|
|
}
|
|
|
|
impl ClientType {
|
|
pub async fn get_versions(&self, file: Option<&PathBuf>) -> PolyResult<Vec<Version>> {
|
|
match self {
|
|
ClientType::Vanilla(_) => vanilla::get_versions(file).await
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
pub trait ClientTrait<'a>: Send + Sync {
|
|
fn get_impl(client: &'a ClientType, instance: &'a Instance, manifest: &'a Manifest) -> Box<dyn ClientTrait<'a> + 'a> where Self: Sized {
|
|
Box::new(match client {
|
|
ClientType::Vanilla(_) => vanilla::VanillaClient::new(instance, manifest)
|
|
})
|
|
}
|
|
|
|
fn new(instance: &'a Instance, manifest: &'a Manifest) -> Self where Self: Sized;
|
|
|
|
async fn launch(&self) -> PolyResult<()>;
|
|
async fn setup(&self) -> PolyResult<()>;
|
|
|
|
async fn install_game(&self) -> PolyResult<()>;
|
|
async fn install_libraries(&self) -> PolyResult<String>;
|
|
async fn install_natives(&self) -> PolyResult<()>;
|
|
async fn install_assets(&self) -> PolyResult<()>;
|
|
|
|
fn get_instance(&self) -> &'a Instance where Self: Sized;
|
|
fn get_manifest(&self) -> &'a Manifest where Self: Sized;
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! create_game_client {
|
|
($props:ident { $($props_name:ident: $props_type:ty),* } $client:ident { $($name:ident: $type:ty),* }) => {
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
pub struct $props {
|
|
$(pub $props_name: $props_type),*
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct $client<'a> {
|
|
pub instance: &'a crate::game::client::Instance,
|
|
pub manifest: &'a crate::game::client::Manifest,
|
|
$(pub $name: $type),*
|
|
}
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! impl_game_client {
|
|
() => {
|
|
fn get_instance(&self) -> &'a crate::game::client::Instance where Self: Sized {
|
|
self.instance
|
|
}
|
|
|
|
fn get_manifest(&self) -> &'a crate::game::client::Manifest where Self: Sized {
|
|
self.manifest
|
|
}
|
|
};
|
|
}
|
|
|
|
pub struct ClientManager {
|
|
instances: HashMap<Uuid, Instance>,
|
|
manifests: HashMap<Uuid, Manifest>,
|
|
}
|
|
|
|
impl ClientManager {
|
|
pub fn new() -> PolyResult<Self> {
|
|
let instances = load_and_serialize::<Instance>(
|
|
&dirs::instances_dir()?
|
|
)?;
|
|
|
|
let manifests = load_and_serialize::<Manifest>(
|
|
&dirs::manifests_dir()?
|
|
)?;
|
|
|
|
Ok(Self {
|
|
instances,
|
|
manifests,
|
|
})
|
|
}
|
|
|
|
pub fn get_instances(&self) -> Vec<&Instance> {
|
|
self.instances.values().collect()
|
|
}
|
|
|
|
pub fn get_instances_owned(&self) -> Vec<Instance> {
|
|
self.instances.values().cloned().collect()
|
|
}
|
|
|
|
pub fn get_instance(&self, uuid: Uuid) -> Option<&Instance> {
|
|
self.instances.get(&uuid)
|
|
}
|
|
|
|
pub fn get_instance_mut(&mut self, uuid: Uuid) -> Option<&mut Instance> {
|
|
self.instances.get_mut(&uuid)
|
|
}
|
|
|
|
pub fn get_manifest(&self, uuid: Uuid) -> Option<&Manifest> {
|
|
self.manifests.get(&uuid)
|
|
}
|
|
|
|
pub fn get_manifest_mut(&mut self, uuid: Uuid) -> Option<&mut Manifest> {
|
|
self.manifests.get_mut(&uuid)
|
|
}
|
|
|
|
pub async fn create_instance(&mut self,
|
|
name: String,
|
|
version: String,
|
|
cover: Option<String>,
|
|
group: Option<Uuid>,
|
|
client: ClientType
|
|
) -> PolyResult<Uuid> {
|
|
let version_cache = dirs::app_config_dir()?.join("versions_cache.json");
|
|
let versions = client.get_versions(Some(&version_cache)).await?;
|
|
let url = &versions.iter().find(|v| v.id == version).ok_or(anyhow!("Version not found"))?.url;
|
|
|
|
let uuid = Uuid::new_v4();
|
|
|
|
// Save the manifest
|
|
let minecraft_manifest = vanilla::retrieve_version_manifest(url).await?;
|
|
let manifest = Manifest {
|
|
id: uuid,
|
|
manifest: minecraft_manifest
|
|
};
|
|
|
|
save(&dirs::manifests_dir()?.join(format!("{}.json", uuid)), &manifest)?;
|
|
self.manifests.insert(uuid, manifest);
|
|
|
|
// Save the instance
|
|
let instance = Instance {
|
|
id: uuid,
|
|
created_at: Local::now(),
|
|
name,
|
|
cover,
|
|
group,
|
|
client
|
|
};
|
|
|
|
save(&dirs::instances_dir()?.join(format!("{}.json", uuid)), &instance)?;
|
|
self.instances.insert(uuid, instance);
|
|
|
|
Ok(uuid)
|
|
}
|
|
}
|
|
|
|
fn save<T>(path: &PathBuf, object: T) -> PolyResult<()> where T: Serialize {
|
|
if !path.exists() {
|
|
fs::create_dir_all(path.parent().expect("Couldn't get parent of path whilst saving object. 'client.rs'"))?;
|
|
}
|
|
|
|
let content = serde_json::to_string(&object)?;
|
|
fs::write(path, content)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn load_and_serialize<T>(dir: &PathBuf) -> PolyResult<HashMap<Uuid, T>> where T: DeserializeOwned {
|
|
let mut result = HashMap::<Uuid, T>::new();
|
|
|
|
if !dir.exists() {
|
|
fs::create_dir_all(dir)?;
|
|
return Ok(result);
|
|
}
|
|
|
|
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 = fs::read_to_string(&path)?;
|
|
let parsed = serde_json::from_str::<T>(&content)?;
|
|
|
|
result.insert(uuid, parsed);
|
|
}
|
|
|
|
Ok(result)
|
|
}
|