Files
STARTLINER/rust/src/util.rs

217 lines
6.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Manager};
use tokio::process::Command;
use std::{path::{Path, PathBuf}, sync::OnceLock};
use crate::model::misc::Game;
#[cfg(not(target_os = "windows"))]
static NAME: &str = "startliner";
#[cfg(target_os = "windows")]
static NAME: &str = "STARTLINER";
#[derive(Clone, Serialize, Deserialize)]
pub struct Dirs {
config_dir: PathBuf,
data_dir: PathBuf,
cache_dir: PathBuf,
}
static DIRS: OnceLock<Dirs> = OnceLock::new();
pub fn init_dirs(apph: &AppHandle) {
DIRS.get_or_init(|| {
if cfg!(windows) {
Dirs {
config_dir: apph.path().data_dir().expect("Unable to set project directories").join(NAME),
data_dir: apph.path().cache_dir().expect("Unable to set project directories").join(NAME).join("data"),
cache_dir: apph.path().cache_dir().expect("Unable to set project directories").join(NAME).join("cache"),
}
} else {
Dirs {
config_dir: apph.path().config_dir().expect("Unable to set project directories").join(NAME),
data_dir: apph.path().data_dir().expect("Unable to set project directories").join(NAME),
cache_dir: apph.path().cache_dir().expect("Unable to set project directories").join(NAME),
}
}
});
}
pub fn all_dirs() -> &'static Dirs {
&DIRS.get().expect("Directories uninitialized")
}
pub fn config_dir() -> &'static Path {
&DIRS.get().expect("Directories uninitialized").config_dir
}
pub fn profile_config_dir(game: Game, name: &str) -> PathBuf {
config_dir().join(format!("profile-{}-{}", game, name))
}
pub fn data_dir() -> &'static Path {
&DIRS.get().expect("Directories uninitialized").data_dir
}
pub fn cache_dir() -> &'static Path {
&DIRS.get().expect("Directories uninitialized").cache_dir
}
pub fn pkg_dir() -> PathBuf {
data_dir().join("pkg")
}
pub fn pkg_dir_of(namespace: &str, name: &str) -> PathBuf {
pkg_dir().join(format!("{}-{}", namespace, name))
}
pub fn copy_directory(src: impl AsRef<Path>, dst: impl AsRef<Path>, 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() {
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.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
#[cfg(target_os = "linux")]
pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
tokio::fs::symlink(src, dst).await
}
#[cfg(target_os = "windows")]
pub async fn symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
//std::os::windows::fs::junction_point(src, dst) // is unstable
junction::create(src, dst)
}
#[cfg(target_os = "windows")]
pub static CREATE_NO_WINDOW: u32 = 0x08000000;
#[cfg(target_os = "windows")]
pub async fn pkill(process_name: &str) {
_ = Command::new("taskkill.exe").arg("/f").arg("/im").arg(process_name)
.creation_flags(CREATE_NO_WINDOW).output().await;
}
#[cfg(target_os = "linux")]
pub async fn pkill(process_name: &str) {
_ = Command::new("pkill").arg(process_name)
.output().await;
}
pub fn clean_up_opts(dir: impl AsRef<Path>) -> Result<()> {
log::debug!("begin clean_up_opts");
if dir.as_ref().is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
log::debug!("{:?}", path);
if path.is_symlink() {
#[cfg(target_os = "windows")]
std::fs::remove_dir(path)?;
#[cfg(not(target_os = "windows"))]
std::fs::remove_file(path)?;
} else {
log::error!("Not a symlink: {:?}", path);
}
}
}
log::debug!("end clean_up_opts");
Ok(())
}
pub trait PathStr {
fn stringify(&self) -> Result<String>;
}
fn path_to_str(p: impl AsRef<Path>) -> Result<String> {
Ok(p.as_ref().to_str().ok_or_else(|| anyhow!("Invalid path: {:?}", p.as_ref()))?.to_owned())
}
impl PathStr for Path {
fn stringify(&self) -> Result<String> {
path_to_str(self)
}
}
impl PathStr for PathBuf {
fn stringify(&self) -> Result<String> {
path_to_str(&self)
}
}
#[allow(dead_code)]
pub fn bool_to_01(val: bool) -> &'static str {
return if val { "1" } else { "0" }
}
// rm -r with checks
pub async fn remove_dir_all(path: impl AsRef<Path>) -> Result<()> {
let canon = path.as_ref().canonicalize()?;
if canon.to_string_lossy().len() < 10 {
return Err(anyhow!("invalid remove_dir_all target: too short"));
}
if canon.starts_with(data_dir().canonicalize()?)
|| canon.starts_with(config_dir().canonicalize()?)
|| canon.starts_with(cache_dir().canonicalize()?) {
tokio::fs::remove_dir_all(path).await
.map_err(|e| anyhow!("invalid remove_dir_all target: {:?}", e))?;
Ok(())
} else {
Err(anyhow!("invalid remove_dir_all target: not in a data directory"))
}
}
#[cfg(target_os = "windows")]
pub fn create_shortcut(
apph: AppHandle,
meta: &crate::profiles::ProfileMeta
) -> Result<()> {
use winsafe::{co, prelude::{ole_IPersistFile, ole_IUnknown, shell_IShellLink}, CoCreateInstance, CoInitializeEx, IPersistFile};
let _com_guard = CoInitializeEx(
co::COINIT::APARTMENTTHREADED
| co::COINIT::DISABLE_OLE1DDE,
)?;
let obj = CoCreateInstance::<winsafe::IShellLink>(
&co::CLSID::ShellLink,
None,
co::CLSCTX::INPROC_SERVER,
)?;
let target_dir = apph.path().cache_dir()?.join(NAME);
let target_path = target_dir.join("startliner.exe");
let lnk_path = apph.path().desktop_dir()?.join(format!("{} {}.lnk", &meta.game.print(), &meta.name));
obj.SetPath(target_path.to_str().ok_or_else(|| anyhow!("Illegal target path"))?)?;
obj.SetDescription(&format!("{} {} (STARTLINER)", &meta.game.print(), &meta.name))?;
obj.SetArguments(&format!("--start --game {} --profile {}", &meta.game, &meta.name))?;
obj.SetIconLocation(
target_dir.join(format!("icon-{}.ico", &meta.game)).to_str().ok_or_else(|| anyhow!("Illegal icon path"))?,
0
)?;
match meta.game {
Game::Ongeki => std::fs::write(target_dir.join("icon-ongeki.ico"), include_bytes!("../../res/icon-ongeki.ico")),
Game::Chunithm => std::fs::write(target_dir.join("icon-chunithm.ico"), include_bytes!("../../res/icon-chunithm.ico"))
}?;
let file = obj.QueryInterface::<IPersistFile>()?;
file.Save(Some(lnk_path.to_str().ok_or_else(|| anyhow!("Illegal shortcut path"))?), true)?;
Ok(())
}