use anyhow::{anyhow, Result};
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use std::sync::OnceLock;
use tokio::sync::Mutex;
use std::env::current_exe;
use std::sync::Arc;
pub struct Osys {
auto_launch: Arc<Mutex<Option<AutoLaunch>>>,
}
impl Osys {
pub fn global() -> &'static Osys {
static SYSOPT: OnceLock<Osys> = OnceLock::new();
SYSOPT.get_or_init(|| Osys {
auto_launch: Arc::new(Mutex::new(None)),
})
}
pub async fn init_launch(&self) -> Result<()> {
let app_exe = current_exe()?;
let app_name = app_exe
.file_stem()
.and_then(|f| f.to_str())
.ok_or(anyhow!("failed to get file stem"))?;
let app_path = app_exe
.as_os_str()
.to_str()
.ok_or(anyhow!("failed to get app_path"))?
.to_string();
#[cfg(target_os = "windows")]
let app_path = format!("\"{app_path}\"");
#[cfg(target_os = "macos")]
let app_path = (|| -> Option<String> {
let path = std::path::PathBuf::from(&app_path);
let path = path.parent()?.parent()?.parent()?;
let extension = path.extension()?.to_str()?;
match extension == "app" {
true => Some(path.as_os_str().to_str()?.to_string()),
false => None,
}
})()
.unwrap_or(app_path);
#[cfg(target_os = "linux")]
let app_path = {
use crate::core::handle::Handle;
use tauri::Manager;
let handle = Handle::global();
match handle.app_handle.lock().as_ref() {
Some(app_handle) => {
let appimage = app_handle.env().appimage;
appimage
.and_then(|p| p.to_str().map(|s| s.to_string()))
.unwrap_or(app_path)
}
None => app_path,
}
};
let auto = AutoLaunchBuilder::new()
.set_app_name(app_name)
.set_app_path(&app_path)
.build()?;
*self.auto_launch.lock().await = Some(auto);
Ok(())
}
pub async fn update_launch(&self, enable: bool) -> Result<()> {
let auto_launch = self.auto_launch.lock().await;
if auto_launch.is_none() {
self.init_launch().await?;
}
match enable {
true => auto_launch.as_ref().unwrap().enable()?,
false => match auto_launch.as_ref().unwrap().disable() {
_ => {}
},
};
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio;
#[tokio::test]
async fn test_osys_singleton() {
let instance1 = Osys::global();
let instance2 = Osys::global();
assert!(std::ptr::eq(instance1, instance2), "应该返回相同的实例");
}
#[tokio::test]
async fn test_init_launch() {
let osys = Osys::global();
let result = osys.init_launch().await;
assert!(result.is_ok(), "初始化应该成功");
let auto_launch = osys.auto_launch.lock().await;
assert!(auto_launch.is_some(), "auto_launch 不应为 None");
}
#[tokio::test]
async fn test_update_launch() {
let osys = Osys::global();
let enable_result = osys.update_launch(true).await;
assert!(enable_result.is_ok(), "启用自动启动应该成功");
let disable_result = osys.update_launch(false).await;
assert!(disable_result.is_ok(), "禁用自动启动应该成功");
}
#[cfg(target_os = "macos")]
#[test]
fn test_macos_app_path_resolution() {
let test_paths = vec![
("/Applications/MyApp.app/Contents/MacOS/binary", "/Applications/MyApp.app"),
("/usr/local/bin/myapp", "/usr/local/bin/myapp"),
(
"/Applications/MyApp.app/Contents/Frameworks/Helper.app/Contents/MacOS/binary",
"/Applications/MyApp.app/Contents/Frameworks/Helper.app"
),
];
for (input, expected) in test_paths {
let result = (|| -> Option<String> {
let path = std::path::PathBuf::from(input);
let mut current = path.as_path();
while let Some(parent) = current.parent() {
if let Some(extension) = current.extension() {
if extension == "app" {
return current.to_str().map(|s| s.to_string());
}
}
current = parent;
}
None
})()
.unwrap_or(input.to_string());
assert_eq!(result, expected);
}
}
}