forked from akanyan/STARTLINER
194 lines
5.7 KiB
Rust
194 lines
5.7 KiB
Rust
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<Self>;
|
|
fn load(name: String) -> Result<Self>;
|
|
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<Self> {
|
|
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<PkgKey> {
|
|
match self {
|
|
Self::OngekiProfile(p) => &p.mods
|
|
}
|
|
}
|
|
pub fn pkgs_mut(&mut self) -> &mut BTreeSet<PkgKey> {
|
|
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<Path>, new_hash: &str) -> Result<bool> {
|
|
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<Vec<ProfileMeta>> {
|
|
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<Path>) -> Option<ProfileMeta> {
|
|
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
|
|
} |