diff --git a/README.md b/README.md index 6b2c6ef..7a4fce2 100644 --- a/README.md +++ b/README.md @@ -262,7 +262,6 @@ npm run tauri build "extraFlags": ["--vfs-cache-mode", "full"] } }, - "auto_mount": true } } ``` @@ -273,7 +272,6 @@ npm run tauri build { "app": { "theme": "auto", - "language": "zh", "auto_update_enabled": true, "monitor_interval": 30000 } diff --git a/README_en.md b/README_en.md index 28e2f8c..bdaae6b 100644 --- a/README_en.md +++ b/README_en.md @@ -262,7 +262,6 @@ Add custom Rclone flags for optimal performance: "extraFlags": ["--vfs-cache-mode", "full"] } }, - "auto_mount": true } } ``` @@ -273,7 +272,6 @@ Add custom Rclone flags for optimal performance: { "app": { "theme": "auto", - "language": "en", "auto_update_enabled": true, "monitor_interval": 30000 } diff --git a/src-tauri/src/cmd/service.rs b/src-tauri/src/cmd/service.rs index 0e639db..cc6bf62 100644 --- a/src-tauri/src/cmd/service.rs +++ b/src-tauri/src/cmd/service.rs @@ -1,11 +1,15 @@ use crate::core::service::check_service_status as check_service_status_impl; use crate::core::service::install_service as install_service_impl; -use crate::core::service::restart_service as restart_service_impl; -use crate::core::service::stop_service as stop_service_impl; +use crate::core::service::start_service as start_service_impl; + use crate::core::service::uninstall_service as uninstall_service_impl; +use crate::object::structs::AppState; +use crate::utils::api::{get_api_key, get_server_port}; +use reqwest; +use tauri::State; #[tauri::command] -pub async fn check_service_status() -> Result { +pub async fn check_service_status() -> Result { check_service_status_impl().await.map_err(|e| e.to_string()) } @@ -20,16 +24,24 @@ pub async fn uninstall_service() -> Result { } #[tauri::command] -pub async fn stop_service() -> Result { - stop_service_impl().await.map_err(|e| e.to_string()) -} - -#[tauri::command] -pub async fn restart_service() -> Result { - restart_service_impl().await.map_err(|e| e.to_string()) +pub async fn stop_service(state: State<'_, AppState>) -> Result { + let api_key = get_api_key(state); + let port = get_server_port(); + let client = reqwest::Client::new(); + let response = client + .post(format!("http://127.0.0.1:{}/api/v1/service/stop", port)) + .header("Authorization", format!("Bearer {}", api_key)) + .send() + .await + .map_err(|e| format!("Failed to send request: {}", e))?; + if response.status().is_success() { + Ok(true) + } else { + Err(format!("Failed to stop service: {}", response.status())) + } } #[tauri::command] pub async fn start_service() -> Result { - check_service_status_impl().await.map_err(|e| e.to_string()) + start_service_impl().await.map_err(|e| e.to_string()) } diff --git a/src-tauri/src/conf/app.rs b/src-tauri/src/conf/app.rs index 75d24bd..a4c4a92 100644 --- a/src-tauri/src/conf/app.rs +++ b/src-tauri/src/conf/app.rs @@ -4,18 +4,14 @@ use serde::{Deserialize, Serialize}; pub struct AppConfig { pub theme: Option, pub monitor_interval: Option, - pub service_api_token: Option, - pub service_port: Option, pub auto_update_enabled: Option, } impl AppConfig { pub fn new() -> Self { Self { - theme: Some("zh".into()), + theme: Some("light".to_string()), monitor_interval: Some(5), - service_api_token: Some("yeM6PCcZGaCpapyBKAbjTp2YAhcku6cUr".into()), - service_port: Some(53211), auto_update_enabled: Some(true), } } diff --git a/src-tauri/src/conf/config.rs b/src-tauri/src/conf/config.rs index f3a6f1a..b998edc 100644 --- a/src-tauri/src/conf/config.rs +++ b/src-tauri/src/conf/config.rs @@ -1,5 +1,6 @@ use crate::conf::rclone::RcloneConfig; use crate::{conf::core::OpenListCoreConfig, utils::path::app_config_file_path}; +use std::path::PathBuf; use super::app::AppConfig; use serde::{Deserialize, Serialize}; @@ -25,20 +26,67 @@ impl MergedSettings { } } + fn get_data_config_path() -> Result { + let app_dir = std::env::current_exe() + .map_err(|e| format!("Failed to get current exe path: {}", e))? + .parent() + .ok_or("Failed to get parent directory")? + .to_path_buf(); + Ok(app_dir.join("data").join("config.json")) + } + + fn read_data_config() -> Result { + let path = Self::get_data_config_path()?; + if !path.exists() { + return Err("data/config.json does not exist".to_string()); + } + + let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?; + serde_json::from_str(&content).map_err(|e| e.to_string()) + } + + fn get_port_from_data_config() -> Result, String> { + let config = Self::read_data_config()?; + Ok(config + .get("scheme") + .and_then(|scheme| scheme.get("http_port")) + .and_then(|port| port.as_u64()) + .map(|port| port as u16)) + } + + pub fn save(&self) -> Result<(), String> { + let path = app_config_file_path().map_err(|e| e.to_string())?; + std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?; + let json = serde_json::to_string_pretty(self).map_err(|e| e.to_string())?; + std::fs::write(&path, json).map_err(|e| e.to_string())?; + Ok(()) + } + pub fn load() -> Result { let path = app_config_file_path().map_err(|e| e.to_string())?; - if !path.exists() { + let mut merged_settings = if !path.exists() { std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?; + let new_settings = Self::new(); std::fs::write( &path, - serde_json::to_string_pretty(&Self::new()).map_err(|e| e.to_string())?, + serde_json::to_string_pretty(&new_settings).map_err(|e| e.to_string())?, ) .map_err(|e| e.to_string())?; - return Ok(Self::new()); + new_settings + } else { + let config = std::fs::read_to_string(path).map_err(|e| e.to_string())?; + serde_json::from_str(&config).map_err(|e| e.to_string())? + }; + + if let Ok(data_port) = Self::get_port_from_data_config() { + if let Some(port) = data_port { + if merged_settings.openlist.port != port { + merged_settings.openlist.port = port; + merged_settings.save()?; + } + } } - let config = std::fs::read_to_string(path).map_err(|e| e.to_string())?; - let merged_settings: MergedSettings = - serde_json::from_str(&config).map_err(|e| e.to_string())?; + Ok(merged_settings) } diff --git a/src-tauri/src/conf/rclone.rs b/src-tauri/src/conf/rclone.rs index 7cec339..fe9328f 100644 --- a/src-tauri/src/conf/rclone.rs +++ b/src-tauri/src/conf/rclone.rs @@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RcloneConfig { pub config: serde_json::Value, - pub flags: Option>, - pub auto_mount: bool, } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -62,8 +60,6 @@ impl RcloneConfig { pub fn new() -> Self { Self { config: serde_json::Value::Object(Default::default()), - flags: None, - auto_mount: false, } } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 606b67f..a74de01 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -203,63 +203,8 @@ fn start_service_with_elevation(service_name: &str) -> Result Result> { - log::info!("Service start not implemented for this platform"); - Ok(false) -} - -#[cfg(target_os = "macos")] -pub async fn install_service() -> Result> { - let app_dir = env::current_exe()?.parent().unwrap().to_path_buf(); - let install_path = app_dir.join("install-openlist-service"); - - if !install_path.exists() { - error!("Service installer not found at {}", install_path.display()); - return Err(Box::from(format!( - "Service installer not found at {}", - install_path.display() - ))); - } - - let status = StdCommand::new(&install_path).status()?; - - if status.success() { - Ok(true) - } else { - Err(Box::from(format!( - "Failed to install service, exit status: {}", - status - ))) - } -} - -#[cfg(target_os = "macos")] -pub async fn uninstall_service() -> Result> { - let app_dir = env::current_exe()?.parent().unwrap().to_path_buf(); - let uninstall_path = app_dir.join("uninstall-openlist-service"); - - if !uninstall_path.exists() { - error!("Uninstaller not found: {:?}", uninstall_path); - return Err(Box::from(format!( - "Uninstaller not found: {:?}", - uninstall_path - ))); - } - let status = StdCommand::new(&uninstall_path).status()?; - - if status.success() { - Ok(true) - } else { - Err(Box::from(format!( - "Failed to uninstall service, exit status: {}", - status - ))) - } -} - #[cfg(target_os = "windows")] -pub async fn check_service_status() -> Result> { +pub async fn start_service() -> Result> { use windows_service::service::{ServiceAccess, ServiceState}; use windows_service::service_manager::{ServiceManager, ServiceManagerAccess}; let service_name = "openlist_desktop_service"; @@ -325,7 +270,7 @@ pub async fn check_service_status() -> Result> } #[cfg(target_os = "linux")] -pub async fn check_service_status() -> Result> { +pub async fn start_service() -> Result> { const SERVICE_NAME: &str = "openlist-desktop-service"; log::info!("Checking Linux service status for: {}", SERVICE_NAME); @@ -333,42 +278,17 @@ pub async fn check_service_status() -> Result> let init_system = detect_linux_init_system(); match init_system.as_str() { - "systemd" => check_systemd_service_status(SERVICE_NAME).await, - "openrc" => check_openrc_service_status(SERVICE_NAME).await, + "systemd" => start_systemd_service_with_check(SERVICE_NAME).await, + "openrc" => start_openrc_service_with_check(SERVICE_NAME).await, _ => { log::warn!("Unknown init system: {}, assuming systemd", init_system); - check_systemd_service_status(SERVICE_NAME).await + start_systemd_service_with_check(SERVICE_NAME).await } } } #[cfg(target_os = "linux")] -fn detect_linux_init_system() -> String { - if std::path::Path::new("/run/systemd/system").exists() { - return "systemd".to_string(); - } - - if std::path::Path::new("/run/openrc").exists() { - return "openrc".to_string(); - } - - if let Ok(output) = StdCommand::new("which").arg("systemctl").output() { - if output.status.success() && !output.stdout.is_empty() { - return "systemd".to_string(); - } - } - - if let Ok(output) = StdCommand::new("which").arg("rc-service").output() { - if output.status.success() && !output.stdout.is_empty() { - return "openrc".to_string(); - } - } - - "systemd".to_string() -} - -#[cfg(target_os = "linux")] -async fn check_systemd_service_status( +async fn start_systemd_service_with_check( service_name: &str, ) -> Result> { log::info!("Checking systemd service status for: {}", service_name); @@ -429,6 +349,249 @@ async fn check_systemd_service_status( } } +#[cfg(target_os = "linux")] +async fn start_openrc_service_with_check( + service_name: &str, +) -> Result> { + log::info!("Checking OpenRC service status for: {}", service_name); + + let status_output = StdCommand::new("rc-service") + .args(&[service_name, "status"]) + .output(); + + match status_output { + Ok(output) => { + let status_str = String::from_utf8_lossy(&output.stdout).to_lowercase(); + let stderr_str = String::from_utf8_lossy(&output.stderr).to_lowercase(); + + log::info!("OpenRC service status output: {}", status_str); + + if status_str.contains("started") || status_str.contains("running") { + log::info!("Service is running"); + return Ok(true); + } else if status_str.contains("stopped") || status_str.contains("inactive") { + log::info!("Service is stopped, attempting to start"); + return start_openrc_service(service_name).await; + } else if stderr_str.contains("does not exist") { + log::error!("Service {} does not exist", service_name); + return Ok(false); + } else { + log::warn!("Unknown service status, attempting to start"); + return start_openrc_service(service_name).await; + } + } + Err(e) => { + log::error!("Failed to check OpenRC service status: {}", e); + return start_openrc_service(service_name).await; + } + } +} + +#[cfg(target_os = "macos")] +pub async fn install_service() -> Result> { + let app_dir = env::current_exe()?.parent().unwrap().to_path_buf(); + let install_path = app_dir.join("install-openlist-service"); + + if !install_path.exists() { + error!("Service installer not found at {}", install_path.display()); + return Err(Box::from(format!( + "Service installer not found at {}", + install_path.display() + ))); + } + + let status = StdCommand::new(&install_path).status()?; + + if status.success() { + Ok(true) + } else { + Err(Box::from(format!( + "Failed to install service, exit status: {}", + status + ))) + } +} + +#[cfg(target_os = "macos")] +pub async fn uninstall_service() -> Result> { + let app_dir = env::current_exe()?.parent().unwrap().to_path_buf(); + let uninstall_path = app_dir.join("uninstall-openlist-service"); + + if !uninstall_path.exists() { + error!("Uninstaller not found: {:?}", uninstall_path); + return Err(Box::from(format!( + "Uninstaller not found: {:?}", + uninstall_path + ))); + } + let status = StdCommand::new(&uninstall_path).status()?; + + if status.success() { + Ok(true) + } else { + Err(Box::from(format!( + "Failed to uninstall service, exit status: {}", + status + ))) + } +} + +#[cfg(target_os = "windows")] +pub async fn check_service_status() -> Result> { + use windows_service::service::{ServiceAccess, ServiceState}; + use windows_service::service_manager::{ServiceManager, ServiceManagerAccess}; + let service_name = "openlist_desktop_service"; + + let manager = match ServiceManager::local_computer( + None::<&str>, + ServiceManagerAccess::CONNECT | ServiceManagerAccess::ENUMERATE_SERVICE, + ) { + Ok(mgr) => mgr, + Err(_) => ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?, + }; + let service = match manager.open_service(service_name, ServiceAccess::QUERY_STATUS) { + Ok(svc) => svc, + Err(e) => { + log::error!("Failed to open service '{}': {:?}", service_name, e); + return Ok("not-installed".to_string()); + } + }; + match service.query_status() { + Ok(status) => match status.current_state { + ServiceState::Running | ServiceState::StartPending => { + return Ok("running".to_string()); + } + ServiceState::StopPending => { + std::thread::sleep(std::time::Duration::from_millis(1000)); + return Ok("stopped".to_string()); + } + _ => { + log::info!("Service is in state: {:?}.", status.current_state); + return Ok("stopped".to_string()); + } + }, + Err(e) => { + log::error!("Failed to query service status: {:?}", e); + match start_service_with_elevation(service_name) { + Ok(true) => Ok("running".to_string()), + Ok(false) => { + log::error!("Failed to start service with elevation."); + Ok("stopped".to_string()) + } + Err(elev_err) => { + log::error!("Error during service elevation: {:?}", elev_err); + Ok("error".to_string()) + } + } + } + } +} + +#[cfg(target_os = "linux")] +pub async fn check_service_status() -> Result> { + const SERVICE_NAME: &str = "openlist-desktop-service"; + + log::info!("Checking Linux service status for: {}", SERVICE_NAME); + + let init_system = detect_linux_init_system(); + + match init_system.as_str() { + "systemd" => check_systemd_service_status(SERVICE_NAME).await, + "openrc" => check_openrc_service_status(SERVICE_NAME).await, + _ => { + log::warn!("Unknown init system: {}, assuming systemd", init_system); + check_systemd_service_status(SERVICE_NAME).await + } + } +} + +#[cfg(target_os = "linux")] +fn detect_linux_init_system() -> String { + if std::path::Path::new("/run/systemd/system").exists() { + return "systemd".to_string(); + } + + if std::path::Path::new("/run/openrc").exists() { + return "openrc".to_string(); + } + + if let Ok(output) = StdCommand::new("which").arg("systemctl").output() { + if output.status.success() && !output.stdout.is_empty() { + return "systemd".to_string(); + } + } + + if let Ok(output) = StdCommand::new("which").arg("rc-service").output() { + if output.status.success() && !output.stdout.is_empty() { + return "openrc".to_string(); + } + } + + "systemd".to_string() +} + +#[cfg(target_os = "linux")] +async fn check_systemd_service_status( + service_name: &str, +) -> Result> { + log::info!("Checking systemd service status for: {}", service_name); + + let status_output = StdCommand::new("systemctl") + .args(&["is-active", service_name]) + .output(); + + match status_output { + Ok(output) => { + let status = String::from_utf8_lossy(&output.stdout) + .trim() + .to_lowercase(); + log::info!("Service {} status: {}", service_name, status); + + match status.as_str() { + "active" | "activating" => { + log::info!("Service is active and running"); + return Ok("running".to_string()); + } + "inactive" | "failed" => { + log::info!("Service is {}", status); + return Ok("stopped".to_string()); + } + "unknown" => { + log::warn!("Service status unknown, checking if service exists"); + let exists_output = StdCommand::new("systemctl") + .args(&["list-unit-files", &format!("{}.service", service_name)]) + .output(); + + match exists_output { + Ok(output) if output.status.success() => { + let output_str = String::from_utf8_lossy(&output.stdout); + if output_str.contains(service_name) { + log::info!("Service exists and not active"); + return Ok("stopped".to_string()); + } else { + log::error!("Service {} not found", service_name); + return Ok("not-installed".to_string()); + } + } + _ => { + log::error!("Failed to check if service exists"); + return Ok("error".to_string()); + } + } + } + _ => { + log::warn!("Unknown service status: {}", status); + return Ok("error".to_string()); + } + } + } + Err(e) => { + log::error!("Failed to check systemd service status: {}", e); + return Ok("error".to_string()); + } + } +} + #[cfg(target_os = "linux")] async fn start_systemd_service(service_name: &str) -> Result> { use users::get_effective_uid; @@ -480,7 +643,7 @@ async fn start_systemd_service(service_name: &str) -> Result Result> { +) -> Result> { log::info!("Checking OpenRC service status for: {}", service_name); let status_output = StdCommand::new("rc-service") @@ -496,21 +659,21 @@ async fn check_openrc_service_status( if status_str.contains("started") || status_str.contains("running") { log::info!("Service is running"); - return Ok(true); + return Ok("running".to_string()); } else if status_str.contains("stopped") || status_str.contains("inactive") { - log::info!("Service is stopped, attempting to start"); - return start_openrc_service(service_name).await; + log::info!("Service is stopped"); + return Ok("stopped".to_string()); } else if stderr_str.contains("does not exist") { log::error!("Service {} does not exist", service_name); - return Ok(false); + return Ok("not-installed".to_string()); } else { log::warn!("Unknown service status, attempting to start"); - return start_openrc_service(service_name).await; + return Ok("error".to_string()); } } Err(e) => { log::error!("Failed to check OpenRC service status: {}", e); - return start_openrc_service(service_name).await; + return Ok("error".to_string()); } } } @@ -562,7 +725,7 @@ async fn start_openrc_service(service_name: &str) -> Result Result> { +pub async fn start_service() -> Result> { const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service"; log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER); @@ -624,6 +787,64 @@ pub async fn check_service_status() -> Result> } } +#[cfg(target_os = "macos")] +pub async fn check_service_status() -> Result> { + const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service"; + + log::info!("Checking macOS service status for: {}", SERVICE_IDENTIFIER); + + let status_output = StdCommand::new("launchctl") + .args(&["list", SERVICE_IDENTIFIER]) + .output(); + + match status_output { + Ok(output) => { + if output.status.success() { + let output_str = String::from_utf8_lossy(&output.stdout); + log::info!("launchctl list output: {}", output_str); + + if let Some(pid_value) = extract_plist_value(&output_str, "PID") { + log::info!("Extracted PID value: {}", pid_value); + if let Ok(pid) = pid_value.parse::() { + if pid > 0 { + log::info!("Service is running with PID: {}", pid); + return Ok("running".to_string()); + } + } + } + + if let Some(exit_status) = extract_plist_value(&output_str, "LastExitStatus") { + if let Ok(status) = exit_status.parse::() { + if status == 0 { + log::info!("Service is loaded but not running (clean exit)"); + return Ok("stopped".to_string()); + } else { + log::warn!("Service has non-zero exit status: {}", status); + return Ok("stopped".to_string()); + } + } + } + + log::info!("Service appears to be loaded but status unclear"); + return Ok("error".to_string()); + } else { + let stderr_str = String::from_utf8_lossy(&output.stderr); + if stderr_str.contains("Could not find service") { + log::error!("Service {} is not loaded", SERVICE_IDENTIFIER); + return Ok("not-installed".to_string()); + } else { + log::warn!("launchctl list failed"); + return Ok("error".to_string()); + } + } + } + Err(e) => { + log::error!("Failed to check macOS service status: {}", e); + return Ok("error".to_string()); + } + } +} + #[cfg(target_os = "macos")] async fn start_macos_service(service_identifier: &str) -> Result> { log::info!("Attempting to start macOS service: {}", service_identifier); @@ -694,412 +915,3 @@ fn extract_plist_value(plist_output: &str, key: &str) -> Option { None } - -#[cfg(target_os = "macos")] -async fn restart_macos_service( - service_identifier: &str, -) -> Result> { - log::info!( - "Attempting to restart macOS service: {}", - service_identifier - ); - - let _ = StdCommand::new("launchctl") - .args(&["stop", service_identifier]) - .status(); - - std::thread::sleep(std::time::Duration::from_millis(500)); - - start_macos_service(service_identifier).await -} - -#[cfg(target_os = "windows")] -pub async fn stop_service() -> Result> { - use deelevate::{PrivilegeLevel, Token}; - use runas::Command as RunasCommand; - use std::os::windows::process::CommandExt; - - let service_name = "openlist_desktop_service"; - log::info!("Attempting to stop Windows service: {}", service_name); - - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - let powershell_cmd = format!("Stop-Service -Name '{}' -Force", service_name); - - let status = match level { - PrivilegeLevel::NotPrivileged => { - log::info!("Running without admin privileges, using runas for elevation"); - RunasCommand::new("powershell.exe") - .args(&["-Command", &powershell_cmd]) - .show(false) - .status()? - } - _ => { - log::info!("Already have admin privileges, running directly"); - StdCommand::new("powershell.exe") - .args(&["-Command", &powershell_cmd]) - .creation_flags(0x08000000) - .status()? - } - }; - - if status.success() { - log::info!("Service stopped successfully"); - std::thread::sleep(std::time::Duration::from_secs(1)); - Ok(true) - } else { - log::error!("Failed to stop service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "linux")] -pub async fn stop_service() -> Result> { - const SERVICE_NAME: &str = "openlist-desktop-service"; - - log::info!("Attempting to stop Linux service: {}", SERVICE_NAME); - - let init_system = detect_linux_init_system(); - - match init_system.as_str() { - "systemd" => stop_systemd_service(SERVICE_NAME).await, - "openrc" => stop_openrc_service(SERVICE_NAME).await, - _ => { - log::warn!("Unknown init system: {}, assuming systemd", init_system); - stop_systemd_service(SERVICE_NAME).await - } - } -} - -#[cfg(target_os = "linux")] -async fn stop_systemd_service(service_name: &str) -> Result> { - use users::get_effective_uid; - - log::info!("Attempting to stop systemd service: {}", service_name); - - let status = match get_effective_uid() { - 0 => StdCommand::new("systemctl") - .args(&["stop", service_name]) - .status()?, - _ => { - let elevator = linux_elevator(); - log::info!("Using {} for elevation", elevator); - - StdCommand::new(&elevator) - .args(&["systemctl", "stop", service_name]) - .status()? - } - }; - - if status.success() { - log::info!("Service stop command completed"); - std::thread::sleep(std::time::Duration::from_secs(1)); - - let verify_output = StdCommand::new("systemctl") - .args(&["is-active", service_name]) - .output()?; - - let verify_status_str = String::from_utf8_lossy(&verify_output.stdout); - let verify_status = verify_status_str.trim(); - let is_stopped = verify_status == "inactive" || verify_status == "failed"; - - if is_stopped { - log::info!("Service verified as stopped"); - } else { - log::warn!( - "Service stop command succeeded but service is still active: {}", - verify_status - ); - } - - Ok(is_stopped) - } else { - log::error!("Failed to stop systemd service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "linux")] -async fn stop_openrc_service(service_name: &str) -> Result> { - use users::get_effective_uid; - - log::info!("Attempting to stop OpenRC service: {}", service_name); - let status = match get_effective_uid() { - 0 => StdCommand::new("rc-service") - .args(&[service_name, "stop"]) - .status()?, - _ => { - let elevator = linux_elevator(); - log::info!("Using {} for elevation", elevator); - - StdCommand::new(&elevator) - .args(&["rc-service", service_name, "stop"]) - .status()? - } - }; - - if status.success() { - log::info!("Service stop command completed"); - std::thread::sleep(std::time::Duration::from_secs(1)); - - let verify_output = StdCommand::new("rc-service") - .args(&[service_name, "status"]) - .output()?; - - let verify_status = String::from_utf8_lossy(&verify_output.stdout).to_lowercase(); - let is_stopped = verify_status.contains("stopped") || verify_status.contains("inactive"); - - if is_stopped { - log::info!("Service verified as stopped"); - } else { - log::warn!( - "Service stop command succeeded but service is still running: {}", - verify_status - ); - } - - Ok(is_stopped) - } else { - log::error!("Failed to stop OpenRC service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "macos")] -pub async fn stop_service() -> Result> { - const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service"; - - log::info!("Attempting to stop macOS service: {}", SERVICE_IDENTIFIER); - - let status = StdCommand::new("launchctl") - .args(&["stop", SERVICE_IDENTIFIER]) - .status()?; - - if status.success() { - log::info!("Service stop command completed"); - std::thread::sleep(std::time::Duration::from_secs(1)); - - let verify_output = StdCommand::new("launchctl") - .args(&["list", SERVICE_IDENTIFIER]) - .output()?; - - if verify_output.status.success() { - let output_str = String::from_utf8_lossy(&verify_output.stdout); - log::info!("Verification output after stop: {}", output_str); - - if let Some(pid_value) = extract_plist_value(&output_str, "PID") { - if let Ok(pid) = pid_value.parse::() { - let is_stopped = pid <= 0; - - if is_stopped { - log::info!("Service verified as stopped"); - } else { - log::warn!( - "Service stop command succeeded but service is still running with PID: {}", - pid - ); - } - - return Ok(is_stopped); - } - } - - log::info!("No PID found in output, service appears to be stopped"); - return Ok(true); - } - - log::info!("Could not verify service status after stop, assuming success"); - Ok(true) - } else { - log::error!("Failed to stop macOS service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "windows")] -pub async fn restart_service() -> Result> { - use deelevate::{PrivilegeLevel, Token}; - use runas::Command as RunasCommand; - use std::os::windows::process::CommandExt; - - let service_name = "openlist_desktop_service"; - log::info!("Attempting to restart Windows service: {}", service_name); - - let powershell_cmd = format!("Restart-Service -Name '{}' -Force", service_name); - - let status = { - let token = Token::with_current_process()?; - let level = token.privilege_level()?; - - match level { - PrivilegeLevel::NotPrivileged => { - log::info!("Running without admin privileges, using runas for elevation"); - RunasCommand::new("powershell.exe") - .args(&["-Command", &powershell_cmd]) - .show(false) - .status()? - } - _ => { - log::info!("Already have admin privileges, running directly"); - StdCommand::new("powershell.exe") - .args(&["-Command", &powershell_cmd]) - .creation_flags(0x08000000) - .status()? - } - } - }; - - if status.success() { - log::info!("Service restart command completed"); - std::thread::sleep(std::time::Duration::from_secs(2)); - - match check_service_status().await { - Ok(true) => { - log::info!("Service verified as running after restart"); - Ok(true) - } - Ok(false) => { - log::warn!("Service restart command succeeded but service is not running"); - Ok(false) - } - Err(e) => { - log::error!("Error verifying service status after restart: {}", e); - Ok(false) - } - } - } else { - log::error!("Failed to restart service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "linux")] -pub async fn restart_service() -> Result> { - const SERVICE_NAME: &str = "openlist-desktop-service"; - - log::info!("Attempting to restart Linux service: {}", SERVICE_NAME); - - let init_system = detect_linux_init_system(); - - match init_system.as_str() { - "systemd" => restart_systemd_service(SERVICE_NAME).await, - "openrc" => restart_openrc_service(SERVICE_NAME).await, - _ => { - log::warn!("Unknown init system: {}, assuming systemd", init_system); - restart_systemd_service(SERVICE_NAME).await - } - } -} - -#[cfg(target_os = "linux")] -async fn restart_systemd_service(service_name: &str) -> Result> { - use users::get_effective_uid; - - log::info!("Attempting to restart systemd service: {}", service_name); - let status = match get_effective_uid() { - 0 => StdCommand::new("systemctl") - .args(&["restart", service_name]) - .status()?, - _ => { - let elevator = linux_elevator(); - log::info!("Using {} for elevation", elevator); - - StdCommand::new(&elevator) - .args(&["systemctl", "restart", service_name]) - .status()? - } - }; - - if status.success() { - log::info!("Service restart command completed"); - std::thread::sleep(std::time::Duration::from_secs(2)); - - let verify_output = StdCommand::new("systemctl") - .args(&["is-active", service_name]) - .output()?; - - let verify_status_str = String::from_utf8_lossy(&verify_output.stdout); - let verify_status = verify_status_str.trim(); - let is_running = verify_status == "active" || verify_status == "activating"; - - if is_running { - log::info!("Service verified as running after restart"); - } else { - log::warn!( - "Service restart command succeeded but service is not active: {}", - verify_status - ); - } - - Ok(is_running) - } else { - log::error!("Failed to restart systemd service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "linux")] -async fn restart_openrc_service(service_name: &str) -> Result> { - use users::get_effective_uid; - - log::info!("Attempting to restart OpenRC service: {}", service_name); - let status = match get_effective_uid() { - 0 => StdCommand::new("rc-service") - .args(&[service_name, "restart"]) - .status()?, - _ => { - let elevator = linux_elevator(); - log::info!("Using {} for elevation", elevator); - - StdCommand::new(&elevator) - .args(&["rc-service", service_name, "restart"]) - .status()? - } - }; - - if status.success() { - log::info!("Service restart command completed"); - std::thread::sleep(std::time::Duration::from_secs(2)); - - let verify_output = StdCommand::new("rc-service") - .args(&[service_name, "status"]) - .output()?; - - let verify_status = String::from_utf8_lossy(&verify_output.stdout).to_lowercase(); - let is_running = verify_status.contains("started") || verify_status.contains("running"); - - if is_running { - log::info!("Service verified as running after restart"); - } else { - log::warn!( - "Service restart command succeeded but service is not running: {}", - verify_status - ); - } - - Ok(is_running) - } else { - log::error!("Failed to restart OpenRC service, exit code: {}", status); - Ok(false) - } -} - -#[cfg(target_os = "macos")] -pub async fn restart_service() -> Result> { - const SERVICE_IDENTIFIER: &str = "io.github.openlistteam.openlist.service"; - - log::info!( - "Attempting to restart macOS service: {}", - SERVICE_IDENTIFIER - ); - - let stop_result = stop_service().await?; - if !stop_result { - log::warn!("Failed to stop service, but continuing with restart attempt"); - } - - std::thread::sleep(std::time::Duration::from_millis(500)); - - start_macos_service(SERVICE_IDENTIFIER).await -} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e1f3535..f68dae8 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -34,8 +34,7 @@ use cmd::http_api::{ }; use cmd::service::{ - check_service_status, install_service, restart_service, start_service, stop_service, - uninstall_service, + check_service_status, install_service, start_service, stop_service, uninstall_service, }; use object::structs::*; @@ -160,7 +159,6 @@ pub fn run() { check_service_status, stop_service, start_service, - restart_service, check_for_updates, download_update, install_update_and_restart, diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index badbbe6..adb1495 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -15,21 +15,31 @@ pub fn create_tray(app_handle: &AppHandle) -> tauri::Result<()> { let hide_i = MenuItem::with_id(app_handle, "hide", "隐藏窗口", true, None::<&str>)?; let restart_i = MenuItem::with_id(app_handle, "restart", "重启应用", true, None::<&str>)?; - let start_service_i = - MenuItem::with_id(app_handle, "start_service", "启动服务", true, None::<&str>)?; - let stop_service_i = - MenuItem::with_id(app_handle, "stop_service", "停止服务", true, None::<&str>)?; + let start_service_i = MenuItem::with_id( + app_handle, + "start_service", + "启动OpenList", + true, + None::<&str>, + )?; + let stop_service_i = MenuItem::with_id( + app_handle, + "stop_service", + "停止OpenList", + true, + None::<&str>, + )?; let restart_service_i = MenuItem::with_id( app_handle, "restart_service", - "重启服务", + "重启OpenList", true, None::<&str>, )?; let service_submenu = Submenu::with_id_and_items( app_handle, "service", - "服务控制", + "核心控制", true, &[&start_service_i, &stop_service_i, &restart_service_i], )?; @@ -113,15 +123,15 @@ fn handle_menu_event(app_handle: &AppHandle, event: tauri::menu::MenuEvent) { } "start_service" => { log::info!("Start service menu item clicked"); - handle_service_action(app_handle, "start"); + handle_core_action(app_handle, "start"); } "stop_service" => { log::info!("Stop service menu item clicked"); - handle_service_action(app_handle, "stop"); + handle_core_action(app_handle, "stop"); } "restart_service" => { log::info!("Restart service menu item clicked"); - handle_service_action(app_handle, "restart"); + handle_core_action(app_handle, "restart"); } _ => { log::debug!("Unknown menu item clicked: {:?}", event.id()); @@ -144,21 +154,21 @@ pub fn update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri: let start_service_i = MenuItem::with_id( app_handle, "start_service", - "启动服务", + "启动OpenList", !service_running, None::<&str>, )?; let stop_service_i = MenuItem::with_id( app_handle, "stop_service", - "停止服务", + "停止OpenList", service_running, None::<&str>, )?; let restart_service_i = MenuItem::with_id( app_handle, "restart_service", - "重启服务", + "重启OpenList", service_running, None::<&str>, )?; @@ -166,7 +176,7 @@ pub fn update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri: let service_submenu = Submenu::with_id_and_items( app_handle, "service", - "服务控制", + "核心控制", true, &[&start_service_i, &stop_service_i, &restart_service_i], )?; @@ -212,14 +222,14 @@ pub fn update_tray_menu_delayed( Ok(()) } -fn handle_service_action(app_handle: &AppHandle, action: &str) { - log::info!("Handling service action from tray: {}", action); +fn handle_core_action(app_handle: &AppHandle, action: &str) { + log::info!("Handling core action from tray: {}", action); - if let Err(e) = app_handle.emit("tray-service-action", action) { - log::error!("Failed to emit tray service action event: {}", e); + if let Err(e) = app_handle.emit("tray-core-action", action) { + log::error!("Failed to emit tray core action event: {}", e); } - log::debug!("Service action '{}' dispatched to frontend", action); + log::debug!("Core action '{}' dispatched to frontend", action); } pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) -> tauri::Result<()> { @@ -227,21 +237,21 @@ pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) -> let start_service_i = MenuItem::with_id( app_handle, "start_service", - "启动服务", + "启动OpenList", !service_running, None::<&str>, )?; let stop_service_i = MenuItem::with_id( app_handle, "stop_service", - "停止服务", + "停止OpenList", service_running, None::<&str>, )?; let restart_service_i = MenuItem::with_id( app_handle, "restart_service", - "重启服务", + "重启OpenList", service_running, None::<&str>, )?; @@ -249,7 +259,7 @@ pub fn force_update_tray_menu(app_handle: &AppHandle, service_running: bool) -> let service_submenu = Submenu::with_id_and_items( app_handle, "service", - "服务控制", + "核心控制", true, &[&start_service_i, &stop_service_i, &restart_service_i], )?; diff --git a/src/App.vue b/src/App.vue index 602870f..d19eb3c 100644 --- a/src/App.vue +++ b/src/App.vue @@ -51,7 +51,7 @@ onMounted(async () => { try { store.init() store.applyTheme(store.settings.app.theme || 'light') - await updateTrayMenu(store.serviceStatus.running) + await updateTrayMenu(store.openlistCoreStatus.running) try { updateUnlisten = await TauriAPI.listenToBackgroundUpdateAvailable(updateInfo => { diff --git a/src/api/tauri.ts b/src/api/tauri.ts index 41a41c2..3889dff 100644 --- a/src/api/tauri.ts +++ b/src/api/tauri.ts @@ -6,18 +6,18 @@ import { DownloadProgress, FileItem, MergedSettings, + OpenListCoreStatus, ProcessConfig, ProcessStatus, RcloneMountInfo, RcloneWebdavConfig, - ServiceStatus, TauriResponse, UpdateCheck } from '../types' export class TauriAPI { // openlist desktop service management - static async checkServiceStatus(): Promise { + static async checkServiceStatus(): Promise { return await invoke('check_service_status') } @@ -37,10 +37,6 @@ export class TauriAPI { return await invoke('stop_service') } - static async restartOpenListService(): Promise { - return await invoke('restart_service') - } - // http API management static async getProcessList(): Promise { return await invoke('get_process_list') @@ -68,7 +64,7 @@ export class TauriAPI { return await invoke('create_openlist_core_process', { autoStart }) } - static async getOpenListCoreStatus(): Promise { + static async getOpenListCoreStatus(): Promise { return await invoke('get_openlist_core_status') } @@ -229,7 +225,7 @@ export class TauriAPI { // Tray event listeners static async listenToTrayServiceActions(callback: (action: string) => void) { - return await listen('tray-service-action', event => { + return await listen('tray-core-action', event => { callback(event.payload as string) }) } diff --git a/src/components/dashboard/CoreMonitorCard.vue b/src/components/dashboard/CoreMonitorCard.vue index 2bf22e8..c3a4b7e 100644 --- a/src/components/dashboard/CoreMonitorCard.vue +++ b/src/components/dashboard/CoreMonitorCard.vue @@ -13,7 +13,7 @@
- Port: {{ serviceStatus.port || 5244 }} + Port: {{ openlistCoreStatus.port || 5244 }} @@ -98,7 +98,7 @@ const tooltip = ref({ }) const isCoreRunning = computed(() => store.isCoreRunning) -const serviceStatus = computed(() => store.serviceStatus) +const openlistCoreStatus = computed(() => store.openlistCoreStatus) const avgResponseTime = computed(() => { if (dataPoints.value.length === 0) return 0 @@ -228,7 +228,7 @@ onMounted(async () => { startTime.value = Date.now() } - monitoringInterval.value = window.setInterval(checkServiceHealth, 2000) + monitoringInterval.value = window.setInterval(checkServiceHealth, (store.settings.app.monitor_interval || 5) * 1000) window.addEventListener('resize', updateChartSize) }) diff --git a/src/components/dashboard/QuickActionsCard.vue b/src/components/dashboard/QuickActionsCard.vue index bf493ce..ae9a3fa 100644 --- a/src/components/dashboard/QuickActionsCard.vue +++ b/src/components/dashboard/QuickActionsCard.vue @@ -75,11 +75,6 @@ {{ t('dashboard.quickActions.autoLaunch') }} - -
@@ -112,24 +107,26 @@ const serviceButtonIcon = computed(() => { const serviceButtonText = computed(() => { if (loading.value) return t('dashboard.quickActions.processing') - return isCoreRunning.value ? t('dashboard.quickActions.stopService') : t('dashboard.quickActions.startService') + return isCoreRunning.value + ? t('dashboard.quickActions.stopOpenListCore') + : t('dashboard.quickActions.startOpenListCore') }) const toggleCore = async () => { if (isCoreRunning.value) { - await store.stopService() + await store.stopOpenListCore() } else { - await store.startService() + await store.startOpenListCore() } } const restartCore = async () => { - await store.restartService() + await store.restartOpenListCore() } const openWebUI = () => { - if (store.serviceUrl) { - window.open(store.serviceUrl, '_blank') + if (store.openListCoreUrl) { + window.open(store.openListCoreUrl, '_blank') } } @@ -260,7 +257,10 @@ const saveSettings = async () => { onMounted(async () => { await rcloneStore.checkRcloneBackendStatus() - statusCheckInterval = window.setInterval(rcloneStore.checkRcloneBackendStatus, 30000) + statusCheckInterval = window.setInterval( + rcloneStore.checkRcloneBackendStatus, + (store.settings.app.monitor_interval || 5) * 1000 + ) }) onUnmounted(() => { diff --git a/src/components/dashboard/ServiceManagementCard.vue b/src/components/dashboard/ServiceManagementCard.vue index 2b1bb94..b54dab3 100644 --- a/src/components/dashboard/ServiceManagementCard.vue +++ b/src/components/dashboard/ServiceManagementCard.vue @@ -22,7 +22,7 @@ @click="installService" :disabled="actionLoading || serviceStatus === 'installed'" class="action-btn install-btn" - v-if="serviceStatus !== 'running'" + v-if="serviceStatus !== 'running' && serviceStatus !== 'stopped'" > {{ @@ -31,17 +31,31 @@ : t('dashboard.serviceManagement.install') }} + + + + diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue index de84cf6..90ef214 100644 --- a/src/views/SettingsView.vue +++ b/src/views/SettingsView.vue @@ -3,18 +3,7 @@ import { ref, reactive, computed, onMounted, watch } from 'vue' import { useRoute } from 'vue-router' import { useAppStore } from '../stores/app' import { useTranslation } from '../composables/useI18n' -import { - Settings, - Server, - HardDrive, - Save, - RotateCcw, - AlertCircle, - CheckCircle, - Plus, - Trash2, - Play -} from 'lucide-vue-next' +import { Settings, Server, HardDrive, Save, RotateCcw, AlertCircle, CheckCircle, Play } from 'lucide-vue-next' import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart' const store = useAppStore() @@ -31,13 +20,15 @@ const openlistCoreSettings = reactive({ ...store.settings.openlist }) const rcloneSettings = reactive({ ...store.settings.rclone }) const appSettings = reactive({ ...store.settings.app }) +const isOpenListPortChanged = computed(() => { + return openlistCoreSettings.port !== store.settings.openlist.port +}) + watch(autoStartApp, async newValue => { if (newValue) { await enable() - console.log(`registered for autostart? ${await isEnabled()}`) } else { await disable() - console.log(`registered for autostart? ${await isEnabled()}`) } }) @@ -75,15 +66,11 @@ onMounted(async () => { if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false if (!rcloneSettings.config) rcloneSettings.config = {} - if (!rcloneSettings.flags) rcloneSettings.flags = [] - if (rcloneSettings.auto_mount === undefined) rcloneSettings.auto_mount = false rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2) - if (!appSettings.theme) appSettings.theme = 'light' + if (!appSettings.monitor_interval) appSettings.monitor_interval = 5 - if (!appSettings.service_api_token) appSettings.service_api_token = 'yeM6PCcZGaCpapyBKAbjTp2YAhcku6cUr' - if (!appSettings.service_port) appSettings.service_port = 53211 if (appSettings.auto_update_enabled === undefined) appSettings.auto_update_enabled = true }) @@ -158,14 +145,6 @@ const handleReset = async () => { messageType.value = 'error' } } - -const addRcloneFlag = () => { - rcloneSettings.flags.push('') -} - -const removeRcloneFlag = (index: number) => { - rcloneSettings.flags.splice(index, 1) -}