From 90ba27c9678c94b2ba7033e6db587b74a59516d9 Mon Sep 17 00:00:00 2001 From: akanyan Date: Fri, 14 Mar 2025 00:23:47 +0000 Subject: [PATCH] feat: the other profile buttons --- rust/src/cmd.rs | 49 ++++++++++++++++++++++++++--- rust/src/download_handler.rs | 2 +- rust/src/lib.rs | 2 ++ rust/src/modules/package.rs | 2 +- rust/src/profiles/mod.rs | 14 ++++++++- rust/src/profiles/ongeki.rs | 16 +++++----- rust/src/util.rs | 14 ++++++--- src/components/ProfileListEntry.vue | 23 +++++++++++--- src/main.ts | 2 ++ src/stores.ts | 10 +++--- src/util.ts | 11 +++++-- 11 files changed, 112 insertions(+), 33 deletions(-) diff --git a/rust/src/cmd.rs b/rust/src/cmd.rs index cecac67..f843428 100644 --- a/rust/src/cmd.rs +++ b/rust/src/cmd.rs @@ -7,7 +7,7 @@ use crate::model::misc::Game; use crate::pkg::{Package, PkgKey}; use crate::pkg_store::InstallResult; use crate::profiles::ongeki::OngekiProfile; -use crate::profiles::{AnyProfile, Profile, ProfileMeta, ProfilePaths}; +use crate::profiles::{self, AnyProfile, Profile, ProfileMeta, ProfilePaths}; use crate::appdata::AppData; use crate::util; @@ -159,24 +159,63 @@ pub async fn rename_profile( let new_meta = ProfileMeta { game: profile.game.clone(), - name: name.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", &name)); + 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, &name, e); + 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(name); + 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>, 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; } } diff --git a/rust/src/download_handler.rs b/rust/src/download_handler.rs index a6d5b94..233424c 100644 --- a/rust/src/download_handler.rs +++ b/rust/src/download_handler.rs @@ -51,7 +51,7 @@ impl DownloadHandler { cache_file_w.sync_all().await?; tokio::fs::rename(&zip_path_part, &zip_path).await?; - log::debug!("Downloaded to {}", zip_path.to_string_lossy()); + log::debug!("Downloaded to {:?}", zip_path); app.emit("download-end", pkg_key)?; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index fa395bd..fb073e4 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -154,6 +154,8 @@ pub async fn run(_args: Vec) { cmd::init_profile, cmd::load_profile, cmd::rename_profile, + cmd::duplicate_profile, + cmd::delete_profile, cmd::get_current_profile, cmd::save_current_profile, diff --git a/rust/src/modules/package.rs b/rust/src/modules/package.rs index 0aa070c..4d9ace1 100644 --- a/rust/src/modules/package.rs +++ b/rust/src/modules/package.rs @@ -25,7 +25,7 @@ pub async fn prepare_packages<'a>(p: &'a impl ProfilePaths, pkgs: &BTreeSet { - p.name = Some(name); + p.name = Some(fixed_name(&ProfileMeta { name, game: Game::Ongeki }, false)); } } } @@ -159,4 +159,16 @@ fn meta_from_path(path: impl AsRef) -> Option { } 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 } \ No newline at end of file diff --git a/rust/src/profiles/ongeki.rs b/rust/src/profiles/ongeki.rs index 327294c..0286228 100644 --- a/rust/src/profiles/ongeki.rs +++ b/rust/src/profiles/ongeki.rs @@ -3,6 +3,7 @@ use tauri::AppHandle; use tauri::Emitter; use std::{collections::BTreeSet, path::PathBuf, process::Stdio}; use crate::model::config::BepInEx; +use crate::profiles::fixed_name; use crate::util::PathStr; use crate::{model::{config::{Display, DisplayMode, Network, Segatools}, misc::Game, segatools_base::segatools_base}, pkg::PkgKey, util}; use super::{Profile, ProfileMeta, ProfilePaths}; @@ -27,14 +28,10 @@ pub struct OngekiProfile { impl Profile for OngekiProfile { fn new(name: String) -> Result { - let mut fixed_name = name.trim().replace(" ", "-"); - - while util::profile_config_dir(&Game::Ongeki, &name).exists() { - fixed_name = format!("new-{}", name); - } + let name = fixed_name(&ProfileMeta { name, game: Game::Ongeki }, true); let p = OngekiProfile { - name: Some(fixed_name), + name: Some(name.clone()), mods: BTreeSet::new(), sgt: Segatools::default(), display: Display::default(), @@ -43,7 +40,8 @@ impl Profile for OngekiProfile { }; p.save()?; std::fs::write(p.config_dir().join("segatools-base.ini"), segatools_base())?; - log::debug!("created profile-ongeki-{}", name); + log::debug!("created profile-ongeki-{}", &name); + Ok(p) } @@ -71,7 +69,7 @@ impl Profile for OngekiProfile { } std::fs::write(&path, s) .map_err(|e| anyhow!("error when writing to {:?}: {}", path, e))?; - log::info!("Written to {}", path.to_string_lossy()); + log::info!("Written to {:?}", path); Ok(()) } @@ -79,7 +77,7 @@ impl Profile for OngekiProfile { async fn start(&self, app: AppHandle) -> Result<()> { let ini_path = self.data_dir().join("segatools.ini"); - log::debug!("With path {}", ini_path.to_string_lossy()); + log::debug!("With path {:?}", ini_path); let mut game_builder; let mut amd_builder; diff --git a/rust/src/util.rs b/rust/src/util.rs index 2185190..75688c0 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -66,15 +66,19 @@ pub fn pkg_dir_of(namespace: &str, name: &str) -> PathBuf { pkg_dir().join(format!("{}-{}", namespace, name)) } -pub fn copy_recursive(src: &Path, dst: &Path) -> std::io::Result<()> { - std::fs::create_dir_all(&dst).unwrap(); - for entry in std::fs::read_dir(src)? { +pub fn copy_directory(src: impl AsRef, dst: impl AsRef, recursive: bool) -> std::io::Result<()> { + std::fs::create_dir_all(dst.as_ref()).unwrap(); + for entry in std::fs::read_dir(src.as_ref())? { let entry = entry?; let meta = entry.metadata()?; if meta.is_dir() { - copy_recursive(&entry.path(), &dst.join(entry.file_name()))?; + if recursive == true { + copy_directory(&entry.path(), &dst.as_ref().join(entry.file_name()), true)?; + } else { + log::warn!("Skipping directory {:?}", meta); + } } else { - std::fs::copy(&entry.path(), &dst.join(entry.file_name()))?; + std::fs::copy(&entry.path(), &dst.as_ref().join(entry.file_name()))?; } } Ok(()) diff --git a/src/components/ProfileListEntry.vue b/src/components/ProfileListEntry.vue index c6d7ac2..16c2b3c 100644 --- a/src/components/ProfileListEntry.vue +++ b/src/components/ProfileListEntry.vue @@ -4,6 +4,7 @@ import Button from 'primevue/button'; import InputText from 'primevue/inputtext'; import * as path from '@tauri-apps/api/path'; import { open } from '@tauri-apps/plugin-shell'; +import { invoke } from '../invoke'; import { useGeneralStore, usePrfStore } from '../stores'; import { ProfileMeta } from '../types'; @@ -19,7 +20,7 @@ if (props.p === undefined) { throw new Error('Invalid ProfileListEntry'); } -const rename = async (event: KeyboardEvent) => { +const renameProfile = async (event: KeyboardEvent) => { if (event.key !== 'Enter') { return; } @@ -32,14 +33,28 @@ const rename = async (event: KeyboardEvent) => { typeof event.target.value === 'string' ) { const value = event.target.value + .trim() + .replaceAll(' ', '-') .replaceAll('..', '') .replaceAll('\\', '') .replaceAll('/', ''); + if (value.length > 0) { await prf.rename(props.p!, value); } } }; + +const duplicateProfile = async () => { + await invoke('duplicate_profile', { profile: props.p }); + await prf.reloadList(); +}; + +const deleteProfile = async () => { + await invoke('delete_profile', { profile: props.p }); + await prf.reloadList(); + await prf.reload(); +};