Files
STARTLINER/rust/src/cmd.rs
2025-03-16 17:55:38 +00:00

348 lines
10 KiB
Rust

use log;
use std::collections::HashMap;
use tokio::sync::Mutex;
use tokio::fs;
use tauri::{AppHandle, Manager, State};
use crate::model::misc::Game;
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::model::misc::StartCheckError;
use crate::util;
#[tauri::command]
pub async fn start_check(state: State<'_, Mutex<AppData>>) -> Result<Vec<StartCheckError>, String> {
log::debug!("invoke: start_check");
let appd = state.lock().await;
let prf = appd.profile.as_ref().ok_or_else(|| format!("No profile to check"))?;
let pkgs = appd.pkgs.get_all();
let mut res = Vec::new();
for key in prf.mod_pkgs() {
if let Some(pkg) = pkgs.get(key) {
if let Some(loc) = &pkg.loc {
for dep in &loc.dependencies {
if !prf.mod_pkgs().contains(dep) {
res.push(StartCheckError::MissingDependency(key.clone(), dep.clone()));
}
}
} else {
res.push(StartCheckError::MissingLocalPackage(key.clone()));
}
} else {
res.push(StartCheckError::MissingRemotePackage(key.clone()));
}
}
for key in prf.special_pkgs() {
log::debug!("special: {}", key);
if let Some(pkg) = pkgs.get(&key) {
if pkg.loc.is_some() {
continue;
}
}
res.push(StartCheckError::MissingTool(key));
}
Ok(res)
}
#[tauri::command]
pub async fn startline(app: AppHandle) -> Result<(), String> {
log::debug!("invoke: startline");
let state = app.state::<Mutex<AppData>>();
let mut hash = "".to_owned();
let mut appd = state.lock().await;
if let Some(p) = &appd.profile {
hash = appd.sum_packages(p);
}
if let Some(p) = &mut appd.profile {
log::debug!("{}", hash);
p.line_up(hash, app.clone()).await
.map_err(|e| format!("Lineup failed:\n{}", e))?;
p.start(app.clone()).await
.map_err(|e| format!("Startup failed:\n{}", e))?;
Ok(())
} else {
Err("No profile".to_owned())
}
}
#[tauri::command]
pub async fn kill() -> Result<(), String> {
util::pkill("amdaemon.exe").await;
// The start routine will kill the other process
Ok(())
}
#[tauri::command]
pub async fn install_package(
state: State<'_, tokio::sync::Mutex<AppData>>,
key: PkgKey,
force: bool
) -> Result<InstallResult, String> {
log::debug!("invoke: install_package({})", key);
let mut appd = state.lock().await;
appd.pkgs.install_package(&key, force, true)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn delete_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey) -> Result<(), String> {
log::debug!("invoke: delete_package({})", key);
let mut appd = state.lock().await;
appd.pkgs.delete_package(&key, true)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn get_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey) -> Result<Package, String> {
log::debug!("invoke: get_package({})", key);
let appd = state.lock().await;
appd.pkgs.get(&key)
.map_err(|e| e.to_string())
.cloned()
}
#[tauri::command]
pub async fn toggle_package(state: State<'_, tokio::sync::Mutex<AppData>>, key: PkgKey, enable: bool) -> Result<(), String> {
log::debug!("invoke: toggle_package({}, {})", key, enable);
let mut appd = state.lock().await;
appd.toggle_package(key, enable)
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn reload_all_packages(state: State<'_, tokio::sync::Mutex<AppData>>) -> Result<(), String> {
log::debug!("invoke: reload_all_packages");
let mut appd = state.lock().await;
appd.pkgs.reload_all()
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn get_all_packages(state: State<'_, Mutex<AppData>>) -> Result<HashMap<PkgKey, Package>, ()> {
log::debug!("invoke: get_all_packages");
let appd = state.lock().await;
Ok(appd.pkgs.get_all())
}
#[tauri::command]
pub async fn fetch_listings(state: State<'_, Mutex<AppData>>) -> Result<(), String> {
log::debug!("invoke: fetch_listings");
{
let appd = state.lock().await;
if !appd.pkgs.is_offline() {
log::warn!("fetch_listings: already done");
return Ok(());
}
if appd.cfg.offline_mode {
log::info!("fetch_listings: skipped");
return Err("offline mode".to_owned());
}
}
let listings = PackageStore::fetch_listings().await
.map_err(|e| e.to_string())?;
let mut appd = state.lock().await;
appd.pkgs.process_fetched_listings(listings);
Ok(())
}
#[tauri::command]
pub async fn list_profiles() -> Result<Vec<ProfileMeta>, String> {
log::debug!("invoke: list_profiles");
let list = crate::profiles::list_profiles().await.map_err(|e| e.to_string())?;
Ok(list)
}
#[tauri::command]
pub async fn init_profile(
state: State<'_, Mutex<AppData>>,
game: Game,
name: String
) -> Result<(), String> {
log::debug!("invoke: init_profile({}, {})", game, name);
let mut appd = state.lock().await;
let new_profile = OngekiProfile::new(name)
.map_err(|e| format!("Unable to create profile: {}", e))?;
fs::create_dir_all(new_profile.config_dir()).await
.map_err(|e| format!("Unable to create the profile config directory: {}", e))?;
fs::create_dir_all(new_profile.data_dir()).await
.map_err(|e| format!("Unable to create the profile data directory: {}", e))?;
appd.profile = Some(AnyProfile::OngekiProfile(new_profile.clone()));
Ok(())
}
#[tauri::command]
pub async fn load_profile(state: State<'_, Mutex<AppData>>, game: Game, name: String) -> Result<(), String> {
log::debug!("invoke: load_profile({} {:?})", game, name);
let mut appd = state.lock().await;
appd.switch_profile(game, name).map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
pub async fn rename_profile(
state: State<'_, Mutex<AppData>>,
profile: ProfileMeta,
name: String
) -> Result<(), String> {
log::debug!("invoke: rename_profile({:?} {:?})", profile, name);
let new_meta = ProfileMeta {
game: profile.game.clone(),
name: profiles::fixed_name(&ProfileMeta { game: profile.game.clone(), name }, false)
};
if new_meta.name == profile.name {
return Ok(());
}
if new_meta.config_dir().exists() {
return Err(format!("Profile {} already exists", &new_meta.name));
}
fs::rename(profile.config_dir(), new_meta.config_dir()).await
.map_err(|e| format!("Unable to rename: {}", e))?;
if let Err(e) = fs::rename(profile.data_dir(), new_meta.data_dir()).await {
log::warn!("Unable to move data dir {}->{}: {}", &profile.name, &new_meta.name, e);
}
let mut appd = state.lock().await;
if let Some(current) = &mut appd.profile {
if current.meta() == profile {
current.rename(new_meta.name);
}
}
Ok(())
}
#[tauri::command]
pub async fn duplicate_profile(profile: ProfileMeta) -> Result<(), String> {
log::debug!("invoke: duplicate_profile({:?})", profile);
let new_meta = ProfileMeta {
game: profile.game.clone(),
name: profiles::fixed_name(&profile, true)
};
util::copy_directory(profile.config_dir(), new_meta.config_dir(), false)
.map_err(|e| format!("Unable to duplicate: {}", e))?;
Ok(())
}
#[tauri::command]
pub async fn delete_profile(state: State<'_, Mutex<AppData>>, profile: ProfileMeta) -> Result<(), String> {
log::debug!("invoke: delete_profile({:?})", profile);
std::fs::remove_dir_all(profile.config_dir())
.map_err(|e| format!("Unable to delete {:?}: {}", profile.config_dir(), e))?;
if let Err(e) = std::fs::remove_dir_all(profile.data_dir()) {
log::warn!("Unable to delete: {:?} {}", profile.data_dir(), e);
}
let mut appd = state.lock().await;
if let Some(current) = &mut appd.profile {
if current.meta() == profile {
appd.profile = None;
}
}
Ok(())
}
#[tauri::command]
pub async fn get_current_profile(state: State<'_, Mutex<AppData>>) -> Result<Option<AnyProfile>, ()> {
log::debug!("invoke: get_current_profile");
let appd = state.lock().await;
Ok(appd.profile.clone())
}
#[tauri::command]
pub async fn save_current_profile(state: State<'_, Mutex<AppData>>, profile: AnyProfile) -> 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(())
}
#[tauri::command]
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
log::debug!("invoke: list_platform_capabilities");
#[cfg(target_os = "windows")]
return Ok(vec!["display".to_owned()]);
#[cfg(target_os = "linux")]
return Ok(vec!["wine".to_owned()]);
}
#[tauri::command]
#[cfg(target_os = "windows")]
pub async fn list_displays() -> Result<Vec<(String, String)>, String> {
use winsafe::prelude::NativeBitflag;
log::debug!("invoke: list_displays");
let mut res = Vec::new();
for displ_dev in winsafe::EnumDisplayDevices(None, None) {
if let Ok(displ_dev) = displ_dev {
if displ_dev.StateFlags.has(winsafe::co::DISPLAY_DEVICE::ATTACHED_TO_DESKTOP) {
res.push((displ_dev.DeviceName(), displ_dev.DeviceString()));
}
} else {
break;
}
}
Ok(res)
}
#[tauri::command]
#[cfg(not(target_os = "windows"))]
pub async fn list_displays() -> Result<Vec<String>, ()> {
log::debug!("invoke: list_displays");
Ok(Vec::new())
}
#[tauri::command]
pub async fn list_directories() -> Result<util::Dirs, ()> {
log::debug!("invoke: list_directores");
Ok(util::all_dirs().clone())
}