feat: optimize service control (#2)

* refactor: remove unused flags from RcloneConfig and related components; update monitoring intervals to use dynamic settings

* fix: update auto-launch title to indicate immediate effect

* refactor: remove service API token and port from AppConfig and related components

* refactor: remove auto_mount property from RcloneConfig and related components

* refactor: rename service to OpenList Core and update related components
This commit is contained in:
Kuingsmile
2025-06-27 14:02:36 +08:00
committed by GitHub
parent 8ccd04b180
commit bd8e54aa42
23 changed files with 582 additions and 766 deletions

View File

@@ -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<bool, String> {
pub async fn check_service_status() -> Result<String, String> {
check_service_status_impl().await.map_err(|e| e.to_string())
}
@@ -20,16 +24,24 @@ pub async fn uninstall_service() -> Result<bool, String> {
}
#[tauri::command]
pub async fn stop_service() -> Result<bool, String> {
stop_service_impl().await.map_err(|e| e.to_string())
}
#[tauri::command]
pub async fn restart_service() -> Result<bool, String> {
restart_service_impl().await.map_err(|e| e.to_string())
pub async fn stop_service(state: State<'_, AppState>) -> Result<bool, String> {
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<bool, String> {
check_service_status_impl().await.map_err(|e| e.to_string())
start_service_impl().await.map_err(|e| e.to_string())
}

View File

@@ -4,18 +4,14 @@ use serde::{Deserialize, Serialize};
pub struct AppConfig {
pub theme: Option<String>,
pub monitor_interval: Option<u64>,
pub service_api_token: Option<String>,
pub service_port: Option<u64>,
pub auto_update_enabled: Option<bool>,
}
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),
}
}

View File

@@ -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<PathBuf, String> {
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<serde_json::Value, String> {
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<Option<u16>, 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<Self, String> {
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)
}

View File

@@ -5,8 +5,6 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RcloneConfig {
pub config: serde_json::Value,
pub flags: Option<Vec<String>>,
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,
}
}
}

View File

@@ -203,63 +203,8 @@ fn start_service_with_elevation(service_name: &str) -> Result<bool, Box<dyn std:
}
}
#[cfg(not(target_os = "windows"))]
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
log::info!("Service start not implemented for this platform");
Ok(false)
}
#[cfg(target_os = "macos")]
pub async fn install_service() -> Result<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>>
}
#[cfg(target_os = "linux")]
pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>> {
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>>
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
use users::get_effective_uid;
@@ -480,7 +643,7 @@ async fn start_systemd_service(service_name: &str) -> Result<bool, Box<dyn std::
#[cfg(target_os = "linux")]
async fn check_openrc_service_status(
service_name: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
) -> Result<String, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::e
}
#[cfg(target_os = "macos")]
pub async fn check_service_status() -> Result<bool, Box<dyn std::error::Error>> {
pub async fn start_service() -> Result<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>>
}
}
#[cfg(target_os = "macos")]
pub async fn check_service_status() -> Result<String, Box<dyn std::error::Error>> {
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::<i32>() {
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::<i32>() {
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<bool, Box<dyn std::error::Error>> {
log::info!("Attempting to start macOS service: {}", service_identifier);
@@ -694,412 +915,3 @@ fn extract_plist_value(plist_output: &str, key: &str) -> Option<String> {
None
}
#[cfg(target_os = "macos")]
async fn restart_macos_service(
service_identifier: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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::<i32>() {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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<bool, Box<dyn std::error::Error>> {
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
}

View File

@@ -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,

View File

@@ -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],
)?;