use anyhow::{Result, anyhow}; use ongeki::OngekiProfile; use serde::{Deserialize, Serialize}; use tauri::AppHandle; use std::{collections::BTreeSet, path::{Path, PathBuf}}; use crate::{model::misc::Game, modules::package::prepare_packages, pkg::PkgKey, util}; pub mod ongeki; #[derive(Deserialize, Serialize, Clone)] pub enum AnyProfile { OngekiProfile(OngekiProfile) } #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] pub struct ProfileMeta { pub game: Game, pub name: String } pub trait Profile: Sized { fn new(name: String) -> Result; fn load(name: String) -> Result; fn save(&self) -> Result<()>; async fn start(&self, app: AppHandle) -> Result<()>; } pub trait ProfilePaths { fn config_dir(&self) -> PathBuf; fn data_dir(&self) -> PathBuf; } impl AnyProfile { pub fn load(game: Game, name: String) -> Result { Ok(match game { Game::Ongeki => AnyProfile::OngekiProfile(OngekiProfile::load(name)?), Game::Chunithm => panic!("Not implemented") }) } pub fn save(&self) -> Result<()> { match self { Self::OngekiProfile(p) => p.save() } } pub fn meta(&self) -> ProfileMeta { match self { Self::OngekiProfile(p) => { ProfileMeta { game: Game::Ongeki, name: p.name.as_ref().unwrap().clone() } } } } pub fn rename(&mut self, name: String) { match self { Self::OngekiProfile(p) => { p.name = Some(fixed_name(&ProfileMeta { name, game: Game::Ongeki }, false)); } } } pub fn pkgs(&self) -> &BTreeSet { match self { Self::OngekiProfile(p) => &p.mods } } pub fn pkgs_mut(&mut self) -> &mut BTreeSet { match self { Self::OngekiProfile(p) => &mut p.mods } } pub async fn line_up(&self, pkg_hash: String, _app: AppHandle) -> Result<()> { match self { Self::OngekiProfile(_p) => { #[cfg(target_os = "windows")] let info = _p.display.line_up()?; let res = self.line_up_the_rest(pkg_hash).await; #[cfg(target_os = "windows")] if let Some(info) = info { use crate::model::config::Display; if res.is_ok() { Display::wait_for_exit(_app, info); } else { Display::clean_up(&info)?; } } res } } } async fn line_up_the_rest(&self, pkg_hash: String) -> Result<()> { match self { Self::OngekiProfile(p) => { if !p.data_dir().exists() { tokio::fs::create_dir(p.data_dir()).await?; } let hash_path = p.data_dir().join(".sl-state"); let meta = self.meta(); util::clean_up_opts(p.data_dir().join("option"))?; if Self::hash_check(&hash_path, &pkg_hash).await? == true { prepare_packages(&meta, &p.mods).await .map_err(|e| anyhow!("package configuration failed:\n{:?}", e))?; } let mut ini = p.sgt.line_up(&meta).await .map_err(|e| anyhow!("segatools configuration failed:\n{:?}", e))?; p.network.line_up(&mut ini)?; ini.write_to_file(p.data_dir().join("segatools.ini")) .map_err(|e| anyhow!("Error writing segatools.ini: {}", e))?; p.bepinex.line_up(&meta)?; Ok(()) } } } pub async fn start(&self, app: AppHandle) -> Result<()> { match self { Self::OngekiProfile(p) => p.start(app).await } } async fn hash_check(prev_hash_path: &impl AsRef, new_hash: &str) -> Result { let prev_hash = tokio::fs::read_to_string(&prev_hash_path).await.unwrap_or_default(); if prev_hash != new_hash { log::debug!("state {} -> {}", prev_hash, new_hash); tokio::fs::write(prev_hash_path, new_hash).await .map_err(|e| anyhow!("Unable to write the state file: {}", e))?; Ok(true) } else { Ok(false) } } } pub async fn list_profiles() -> Result> { let path = std::fs::read_dir(util::config_dir())?; let mut res = Vec::new(); for f in path { let f = f?; if let Ok(meta) = f.metadata() { if !meta.is_dir() { continue; } log::debug!("{:?}", f); if let Some(meta) = meta_from_path(f.path()) { res.push(meta); } } } Ok(res) } fn meta_from_path(path: impl AsRef) -> Option { let regex = regex::Regex::new( r"^profile-([^\-]+)-(.+)$" ).expect("Invalid regex"); let fname = path.as_ref().file_name().unwrap_or_default().to_string_lossy(); if let Some(caps) = regex.captures(&fname) { let game = caps.get(1).unwrap().as_str(); let name = caps.get(2).unwrap().as_str().to_owned(); if let Some(game) = Game::from_str(game) { return Some(ProfileMeta { game, name }); } } None } pub fn fixed_name(meta: &ProfileMeta, prepend_new: bool) -> String { let mut name = meta.name.trim() .replace(" ", "-") .replace("..", "").replace("/", "").replace("\\", ""); while prepend_new && util::profile_config_dir(&meta.game, &name).exists() { name = format!("new-{}", name); } name }