feat: misc improvements

This commit is contained in:
2025-03-18 23:27:17 +00:00
parent fe1a32f31b
commit 1191cdd95c
15 changed files with 264 additions and 68 deletions

View File

@ -1,5 +1,6 @@
use std::hash::{DefaultHasher, Hash, Hasher};
use crate::model::config::GlobalConfig;
use crate::pkg::{Feature, Status};
use crate::profiles::AnyProfile;
use crate::{model::misc::Game, pkg::PkgKey};
use crate::pkg_store::PackageStore;
@ -18,6 +19,13 @@ pub struct AppData {
pub state: GlobalState,
}
#[derive(PartialEq, Debug, Copy, Clone)]
pub enum ToggleAction {
Disable,
EnableSelf,
EnableRecursive,
}
impl AppData {
pub fn new(apph: AppHandle) -> AppData {
let cfg = std::fs::read_to_string(util::config_dir().join("config.json"))
@ -59,26 +67,34 @@ impl AppData {
}
}
pub fn toggle_package(&mut self, key: PkgKey, enable: bool) -> Result<()> {
log::debug!("toggle: {} {}", key, enable);
pub fn toggle_package(&mut self, key: PkgKey, action: ToggleAction) -> Result<()> {
log::debug!("toggle: {} {:?}", key, action);
let profile = self.profile.as_mut().ok_or_else(|| anyhow!("No profile"))?;
if enable {
if action != ToggleAction::Disable {
let pkg = self.pkgs.get(&key)?;
let loc = pkg.loc
.clone()
.ok_or_else(|| anyhow!("Attempted to enable a non-existent package"))?;
profile.mod_pkgs_mut().insert(key);
for d in &loc.dependencies {
_ = self.toggle_package(d.clone(), true);
if let Status::OK(feature_set) = loc.status {
log::debug!("{:?}", feature_set);
if feature_set.contains(Feature::Mod) {
profile.mod_pkgs_mut().insert(key);
}
}
if action == ToggleAction::EnableRecursive {
for d in &loc.dependencies {
_ = self.toggle_package(d.clone(), action);
}
}
} else {
profile.mod_pkgs_mut().remove(&key);
for (ckey, pkg) in self.pkgs.get_all() {
if let Some(loc) = pkg.loc {
if loc.dependencies.contains(&key) {
self.toggle_package(ckey, false)?;
self.toggle_package(ckey, action)?;
}
}
}
@ -99,4 +115,10 @@ impl AppData {
}
hasher.finish().to_string()
}
pub fn fix(&mut self) {
if let Some(p) = &mut self.profile {
p.fix(&self.pkgs);
}
}
}

View File

@ -8,7 +8,7 @@ use crate::pkg::{Package, PkgKey};
use crate::pkg_store::{InstallResult, PackageStore};
use crate::profiles::ongeki::OngekiProfile;
use crate::profiles::{self, AnyProfile, Profile, ProfileMeta, ProfilePaths};
use crate::appdata::AppData;
use crate::appdata::{AppData, ToggleAction};
use crate::model::misc::StartCheckError;
use crate::util;
@ -103,6 +103,9 @@ pub async fn delete_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
let mut appd = state.lock().await;
appd.pkgs.delete_package(&key, true)
.await
.map_err(|e| e.to_string())?;
appd.toggle_package(key, ToggleAction::Disable)
.map_err(|e| e.to_string())
}
@ -121,7 +124,7 @@ pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key:
log::debug!("invoke: toggle_package({}, {})", key, enable);
let mut appd = state.lock().await;
appd.toggle_package(key, enable)
appd.toggle_package(key, if enable { ToggleAction::EnableRecursive } else { ToggleAction::Disable })
.map_err(|e| e.to_string())
}
@ -205,6 +208,7 @@ pub async fn load_profile(state: State<'_, Mutex<AppData>>, game: Game, name: St
let mut appd = state.lock().await;
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
appd.fix();
Ok(())
}
@ -290,14 +294,31 @@ pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Opt
}
#[tauri::command]
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>, profile: AnyProfile) -> Result<(), String> {
pub async fn sync_current_profile(state: State<'_, Mutex<AppData>>, profile: AnyProfile) -> Result<(), String> {
log::debug!("invoke: sync_current_profile");
let mut appd = state.lock().await;
if let Some(p) = &mut appd.profile {
p.sync(profile);
}
Ok(())
}
#[tauri::command]
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
log::debug!("invoke: save_current_profile");
let mut appd = state.lock().await;
profile.save().map_err(|e| e.to_string())?;
appd.profile = Some(profile);
Ok(())
appd.fix();
match &mut appd.profile {
Some(p) => {
p.save().map_err(|e| e.to_string())
},
None => {
Err("no profile to save".to_owned())
}
}
}
#[tauri::command]

View File

@ -8,15 +8,19 @@ mod appdata;
mod modules;
mod profiles;
use std::sync::OnceLock;
use anyhow::anyhow;
use closure::closure;
use appdata::AppData;
use appdata::{AppData, ToggleAction};
use model::misc::Game;
use pkg::PkgKey;
use tauri::{AppHandle, Listener, Manager};
use pkg_store::Payload;
use tauri::{AppHandle, Listener, Manager, RunEvent};
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_cli::CliExt;
use tokio::{sync::Mutex, fs, try_join};
use tokio::{fs, sync::Mutex, try_join};
static EXIT_REQUESTED: OnceLock<()> = OnceLock::new();
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub async fn run(_args: Vec<String>) {
@ -30,7 +34,7 @@ pub async fn run(_args: Vec<String>) {
.unwrap_or_default()
);
tauri::Builder::default()
let tauri = tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
let _ = app
.get_webview_window("main")
@ -65,8 +69,8 @@ pub async fn run(_args: Vec<String>) {
} else {
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::App("index.html".into()))
.title("STARTLINER")
.inner_size(600f64, 500f64)
.min_inner_size(600f64, 500f64)
.inner_size(640f64, 480f64)
.min_inner_size(640f64, 480f64)
.build()?;
start_immediately = false;
}
@ -102,17 +106,19 @@ pub async fn run(_args: Vec<String>) {
});
app.listen("download-end", closure!(clone apph, |ev| {
log::debug!("download-end triggered: {}", ev.payload());
let raw = ev.payload();
let key = PkgKey(raw[1..raw.len()-1].to_owned());
let apph = apph.clone();
tauri::async_runtime::spawn(async move {
let mutex = apph.state::<Mutex<AppData>>();
let mut appd = mutex.lock().await;
_ = appd.pkgs.install_package(&key, true, false).await;
log::debug!("download-end install {:?}", appd.pkgs.install_package(&key, true, false).await);
});
}));
app.listen("launch-end", closure!(clone apph, |_| {
log::debug!("launch-end triggered");
let apph = apph.clone();
tauri::async_runtime::spawn(async move {
let mutex = apph.state::<Mutex<AppData>>();
@ -123,6 +129,26 @@ pub async fn run(_args: Vec<String>) {
});
}));
app.listen("install-end-prelude", closure!(clone apph, |ev| {
log::debug!("install-end-prelude triggered: {}", ev.payload());
let payload = serde_json::from_str::<Payload>(ev.payload());
let apph = apph.clone();
if let Ok(payload) = payload {
tauri::async_runtime::spawn(async move {
let mutex = apph.state::<Mutex<AppData>>();
let mut appd = mutex.lock().await;
log::debug!(
"install-end-prelude toggle {:?}",
appd.toggle_package(payload.pkg.clone(), ToggleAction::EnableSelf)
);
use tauri::Emitter;
log::debug!("install-end {:?}", apph.emit("install-end", payload));
});
} else {
log::error!("install-end-prelude: invalid payload: {}", ev.payload());
}
}));
if start_immediately == true {
let apph = apph.clone();
tauri::async_runtime::spawn(async move {
@ -163,14 +189,40 @@ pub async fn run(_args: Vec<String>) {
cmd::duplicate_profile,
cmd::delete_profile,
cmd::get_current_profile,
cmd::sync_current_profile,
cmd::save_current_profile,
cmd::list_displays,
cmd::list_platform_capabilities,
cmd::list_directories,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
.build(tauri::generate_context!())
.expect("error while building tauri application");
tauri.run(move |app, event| {
match event {
RunEvent::ExitRequested { api, code, .. } => {
log::debug!("exit request {:?} {:?}", api, code);
if EXIT_REQUESTED.get().is_none() {
_= EXIT_REQUESTED.set(());
api.prevent_exit();
let app = app.clone();
tauri::async_runtime::spawn(async move {
let mutex = app.state::<Mutex<AppData>>();
let appd = mutex.lock().await;
if let Some(p) = &appd.profile {
log::debug!("save: {:?}", p.save());
app.exit(0);
}
});
}
},
RunEvent::Exit => {
log::info!("exit");
}
_ => {}
}
});
}
fn deep_link(app: AppHandle, args: Vec<String>) {

View File

@ -2,9 +2,10 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::pkg::PkgKey;
#[derive(Deserialize, Serialize, Clone)]
#[derive(Deserialize, Serialize, Clone, Default, PartialEq)]
pub enum Aime {
BuiltIn,
Disabled,
#[default] BuiltIn,
AMNet(PkgKey),
Other(PkgKey),
}
@ -27,7 +28,8 @@ pub struct Segatools {
pub target: PathBuf,
pub hook: Option<PkgKey>,
pub io: Option<PkgKey>,
pub aime: Option<Aime>,
#[serde(default)]
pub aime: Aime,
pub amfs: PathBuf,
pub option: PathBuf,
pub appdata: PathBuf,
@ -45,7 +47,7 @@ impl Default for Segatools {
amfs: PathBuf::default(),
option: PathBuf::default(),
appdata: PathBuf::from("appdata"),
aime: Some(Aime::BuiltIn),
aime: Aime::default(),
intel: false,
amnet: AMNet::default(),
}

View File

@ -60,7 +60,7 @@ impl Network {
cmd.arg(&self.local_path);
cmd.current_dir(artemis_dir);
cmd.spawn()
.map_err(|e| anyhow!("Unable to spawn artemis: {}", e))?;
.map_err(|e| anyhow!("unable to spawn artemis: {}", e))?;
} else {
log::warn!("unable to parse the artemis hostname");
}

View File

@ -3,8 +3,34 @@ use std::path::PathBuf;
use anyhow::{anyhow, Result};
use ini::Ini;
use crate::{model::{profile::{Aime, Segatools}, segatools_base::segatools_base}, profiles::ProfilePaths, util::{self, PathStr}};
use crate::pkg_store::PackageStore;
impl Segatools {
pub fn fix(&mut self, store: &PackageStore) {
macro_rules! remove_if_nonpresent {
($item:expr,$key:expr,$emptyval:expr,$store:expr) => {
if let Ok(pkg) = $store.get($key) {
if pkg.loc.is_none() {
$item = $emptyval;
}
} else {
$item = $emptyval;
}
}
}
if let Some(key) = &self.hook {
remove_if_nonpresent!(self.hook, key, None, store);
}
if let Some(key) = &self.io {
remove_if_nonpresent!(self.io, key, None, store);
}
match &self.aime {
Aime::AMNet(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
Aime::Other(key) => remove_if_nonpresent!(self.aime, key, Aime::BuiltIn, store),
_ => {},
}
}
pub async fn line_up(&self, p: &impl ProfilePaths) -> Result<Ini> {
log::debug!("begin line-up: segatools");
@ -50,11 +76,11 @@ impl Segatools {
pfx_dir.join("BepInEx").join("core").join("BepInEx.Preloader.dll").stringify()?
);
if let Some(aime) = &self.aime {
if self.aime != Aime::Disabled {
ini_out.with_section(Some("aime"))
.set("enable", "1")
.set("aimePath", p.config_dir().join("aime.txt").stringify()?);
if let Aime::AMNet(key) = aime {
if let Aime::AMNet(key) = &self.aime {
let mut aimeio = ini_out.with_section(Some("aimeio"));
aimeio
.set("path", util::pkg_dir().join(key.to_string()).join("segatools").join("aimeio.dll").stringify()?)

View File

@ -20,7 +20,6 @@ pub struct Package {
pub namespace: String,
pub name: String,
pub description: String,
pub icon: String,
pub loc: Option<Local>,
pub rmt: Option<Remote>
}
@ -50,6 +49,7 @@ pub struct Local {
pub path: PathBuf,
pub dependencies: BTreeSet<PkgKey>,
pub status: Status,
pub icon: String,
}
#[derive(Clone, Serialize, Deserialize)]
@ -58,6 +58,7 @@ pub struct Remote {
pub version: String,
pub package_url: String,
pub download_url: String,
pub icon: String,
pub deprecated: bool,
pub nsfw: bool,
pub categories: Vec<String>,
@ -76,11 +77,11 @@ impl Package {
namespace: p.owner,
name: v.name,
description: v.description,
icon: v.icon,
loc: None,
rmt: Some(Remote {
package_url: p.package_url,
download_url: v.download_url,
icon: v.icon,
deprecated: p.is_deprecated,
nsfw: p.has_nsfw_content,
version: v.version_number,
@ -107,10 +108,10 @@ impl Package {
namespace: Self::dir_to_namespace(&dir)?,
name: mft.name.clone(),
description: mft.description.clone(),
icon,
loc: Some(Local {
version: mft.version_number,
path: dir.to_owned(),
icon,
status,
dependencies
}),

View File

@ -22,7 +22,7 @@ pub struct Payload {
pub pkg: PkgKey
}
#[derive(Clone, Serialize, Deserialize)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
pub enum InstallResult {
Ready, Deferred
}
@ -167,7 +167,7 @@ impl PackageStore {
archive.extract(path)?;
self.reload_package(key.to_owned()).await;
self.app.emit("install-end", Payload {
self.app.emit("install-end-prelude", Payload {
pkg: key.to_owned()
})?;
@ -189,7 +189,7 @@ impl PackageStore {
let rv = Self::clean_up_package(&path).await;
if rv.is_ok() {
self.app.emit("install-end", Payload {
self.app.emit("install-end-prelude", Payload {
pkg: key.to_owned()
})?;
log::info!("deleted {}", key);

View File

@ -3,7 +3,7 @@ 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};
use crate::{model::misc::Game, modules::package::prepare_packages, pkg::PkgKey, pkg_store::PackageStore, util};
pub mod ongeki;
@ -83,6 +83,26 @@ impl AnyProfile {
}
res
}
pub fn fix(&mut self, store: &PackageStore) {
match self {
Self::OngekiProfile(p) => p.sgt.fix(store)
}
}
pub fn sync(&mut self, source: AnyProfile) {
match self {
Self::OngekiProfile(p) => {
#[allow(irrefutable_let_patterns)]
if let AnyProfile::OngekiProfile(source) = source {
p.bepinex = source.bepinex;
p.display = source.display;
p.network = source.network;
p.sgt = source.sgt;
} else {
log::error!("sync: invalid profile type {:?}", source);
}
}
}
}
pub async fn line_up(&self, pkg_hash: String, _app: AppHandle) -> Result<()> {
match self {
Self::OngekiProfile(_p) => {