feat: profile imports/exports
This commit is contained in:
@ -410,6 +410,33 @@ pub async fn create_shortcut(app: AppHandle, profile_meta: ProfileMeta) -> Resul
|
||||
util::create_shortcut(app, &profile_meta).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn export_profile(state: State<'_, Mutex<AppData>>, export_keychip: bool, files: Vec<String>) -> Result<(), String> {
|
||||
log::debug!("invoke: export_profile({:?}, {:?} files)", export_keychip, files.len());
|
||||
|
||||
let appd = state.lock().await;
|
||||
match &appd.profile {
|
||||
Some(p) => {
|
||||
p.export(export_keychip, files)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
None => {
|
||||
let err = "export_profile: no profile".to_owned();
|
||||
log::error!("{}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn import_profile(state: State<'_, Mutex<AppData>>, path: PathBuf) -> Result<(), String> {
|
||||
log::debug!("invoke: import_profile({:?})", path);
|
||||
|
||||
Profile::import(path).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_platform_capabilities() -> Result<Vec<String>, ()> {
|
||||
log::debug!("invoke: list_platform_capabilities");
|
||||
|
@ -205,6 +205,8 @@ pub async fn run(_args: Vec<String>) {
|
||||
cmd::save_current_profile,
|
||||
cmd::load_segatools_ini,
|
||||
cmd::create_shortcut,
|
||||
cmd::export_profile,
|
||||
cmd::import_profile,
|
||||
|
||||
cmd::get_global_config,
|
||||
cmd::set_global_config,
|
||||
|
@ -10,6 +10,7 @@ use std::fs::File;
|
||||
use tokio::process::Command;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
pub mod template;
|
||||
pub mod types;
|
||||
|
||||
impl Profile {
|
||||
@ -43,12 +44,6 @@ impl Profile {
|
||||
std::fs::create_dir_all(p.config_dir())?;
|
||||
std::fs::create_dir_all(p.data_dir())?;
|
||||
|
||||
if meta.game == Game::Ongeki {
|
||||
if let Err(e) = Self::load_existing_mu3_ini(&p.data, &p.meta) {
|
||||
log::error!("unable to load existing mu3.ini: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
match meta.game {
|
||||
Game::Ongeki => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-ongeki.ini"))?,
|
||||
Game::Chunithm => std::fs::write(p.config_dir().join("segatools-base.ini"), include_bytes!("../../static/segatools-chunithm.ini"))?,
|
||||
@ -431,12 +426,14 @@ impl Profile {
|
||||
}
|
||||
|
||||
fn load_existing_mu3_ini(data: &ProfileData, meta: &ProfileMeta) -> Result<()> {
|
||||
let mu3_ini_target_path = data.sgt.target.parent().ok_or_else(|| anyhow!("invalid target directory"))?.join("mu3.ini");
|
||||
let mu3_ini_profile_path = util::profile_config_dir(meta.game, &meta.name).join("mu3.ini");
|
||||
log::debug!("mu3.ini paths: {:?} {:?}", mu3_ini_target_path, mu3_ini_profile_path);
|
||||
if mu3_ini_target_path.exists() && !mu3_ini_profile_path.exists() {
|
||||
std::fs::copy(&mu3_ini_target_path, &mu3_ini_profile_path)?;
|
||||
log::info!("copied mu3.ini from {:?}", &mu3_ini_target_path);
|
||||
if let Some(parent) = data.sgt.target.parent() {
|
||||
let mu3_ini_target_path = parent.join("mu3.ini");
|
||||
let mu3_ini_profile_path = util::profile_config_dir(meta.game, &meta.name).join("mu3.ini");
|
||||
log::debug!("mu3.ini paths: {:?} {:?}", mu3_ini_target_path, mu3_ini_profile_path);
|
||||
if mu3_ini_target_path.exists() && !mu3_ini_profile_path.exists() {
|
||||
std::fs::copy(&mu3_ini_target_path, &mu3_ini_profile_path)?;
|
||||
log::info!("copied mu3.ini from {:?}", &mu3_ini_target_path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
90
rust/src/profiles/template.rs
Normal file
90
rust/src/profiles/template.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use std::{fs::File, io::{Read, Write}, path::PathBuf};
|
||||
use zip::{write::FileOptions, ZipArchive, ZipWriter};
|
||||
use crate::util;
|
||||
use super::{Profile, ProfilePaths};
|
||||
|
||||
impl Profile {
|
||||
fn find_template_json(archive: &mut ZipArchive<File>) -> anyhow::Result<String> {
|
||||
if let Ok(mut file) = archive.by_name("template.json") {
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents)?;
|
||||
Ok(String::from_utf8(contents)?)
|
||||
} else {
|
||||
anyhow::bail!("invalid template: no template.json found")
|
||||
}
|
||||
}
|
||||
pub fn import(path: PathBuf) -> anyhow::Result<()> {
|
||||
let file = File::open(path)?;
|
||||
let mut archive = ZipArchive::new(file)?;
|
||||
|
||||
match Self::find_template_json(&mut archive) {
|
||||
Ok(raw_p) => {
|
||||
let p = serde_json::from_str::<Profile>(&raw_p)?;
|
||||
let dir = util::config_dir().join(format!("profile-{}-{}", &p.meta.game, &p.meta.name));
|
||||
if dir.exists() {
|
||||
anyhow::bail!("profile {} already exists", &p.meta.name);
|
||||
}
|
||||
std::fs::create_dir(&dir)?;
|
||||
archive.extract(&dir)?;
|
||||
std::fs::remove_file(dir.join("template.json"))?;
|
||||
std::fs::write(dir.join("profile.json"), serde_json::to_string_pretty(&p.data)?)?;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn export(&self, export_keychip: bool, extra_files: Vec<String>) -> anyhow::Result<()> {
|
||||
let mut prf = self.clone();
|
||||
|
||||
let dir = util::config_dir().join("exports");
|
||||
|
||||
if !dir.exists() {
|
||||
std::fs::create_dir(&dir)?;
|
||||
}
|
||||
|
||||
let path = dir.join(format!("{}-{}-template.zip", &self.meta.game, &self.meta.name));
|
||||
|
||||
{
|
||||
let sgt = &mut prf.data.sgt;
|
||||
sgt.target = PathBuf::new();
|
||||
if sgt.amfs.is_absolute() {
|
||||
sgt.amfs = PathBuf::new();
|
||||
}
|
||||
if sgt.option.is_absolute() {
|
||||
sgt.option = PathBuf::new();
|
||||
}
|
||||
if sgt.appdata.is_absolute() {
|
||||
sgt.appdata = PathBuf::new();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let network = &mut prf.data.network;
|
||||
if network.local_path.is_absolute() {
|
||||
network.local_path = PathBuf::new();
|
||||
}
|
||||
if !export_keychip {
|
||||
network.keychip = String::new();
|
||||
}
|
||||
}
|
||||
|
||||
let file = File::create(&path)?;
|
||||
let mut zip = ZipWriter::new(file);
|
||||
let options: FileOptions<'_, ()> = FileOptions::default();
|
||||
zip.start_file("template.json", options)?;
|
||||
zip.write_all(&serde_json::to_string_pretty(&prf)?.as_bytes())?;
|
||||
|
||||
for file in extra_files {
|
||||
log::debug!("extra file: {file}");
|
||||
zip.start_file(&file, options)?;
|
||||
zip.write_all(&std::fs::read(self.config_dir().join(file))?)?;
|
||||
}
|
||||
|
||||
zip.finish()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "STARTLINER",
|
||||
"version": "0.12.1",
|
||||
"version": "0.13.0",
|
||||
"identifier": "zip.patafour.startliner",
|
||||
"build": {
|
||||
"beforeDevCommand": "bun run dev",
|
||||
|
Reference in New Issue
Block a user