use crate::keyboard_listener::start_keyboard_listener;
use crate::permissions::check_and_request_permissions;
use crate::process::verify_process_running;
use snipt_core::config::{db_file_exists, ensure_config_dir, get_db_file_path, get_pid_file_path};
use snipt_core::{get_config_dir, is_daemon_running, load_snippets, Result, SniptError};
use snipt_server::server::http_server::stop_api_server;
use snipt_server::server::utils::{get_api_server_port, port_is_available, save_api_port};
use std::fs::{self, File};
use std::io::Write;
use std::process;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
pub fn start_daemon(api_port: u16) -> Result<()> {
check_and_request_permissions()?;
if let Some(pid) = is_daemon_running()? {
if verify_process_running(pid) {
println!("Daemon is already running with PID {}.", pid);
} else {
println!("Found stale PID file. Cleaning up and starting new daemon...");
let _ = fs::remove_file(get_pid_file_path());
}
}
if is_daemon_running()?.is_none() {
println!("Starting snipt daemon...");
ensure_config_dir()?;
if !db_file_exists() {
return Err(SniptError::DatabaseNotFound(
get_db_file_path().to_string_lossy().to_string(),
));
}
#[cfg(unix)]
{
use std::process::Command;
let current_exe = std::env::current_exe()?;
let daemon_log_file = format!("{}/daemon_log.txt", get_config_dir().to_string_lossy());
let cmd = format!(
"nohup \"{}\" daemon-worker > \"{}\" 2>&1 &",
current_exe.to_string_lossy(),
daemon_log_file
);
Command::new("sh").arg("-c").arg(&cmd).status()?;
for _ in 0..20 {
thread::sleep(Duration::from_millis(100));
if is_daemon_running()?.is_some() {
break;
}
}
if let Some(pid) = is_daemon_running()? {
if verify_process_running(pid) {
println!("Daemon started successfully with PID {}.", pid);
} else {
return Err(SniptError::Other(format!(
"Daemon process failed to start. Check logs at {}",
daemon_log_file
)));
}
} else {
return Err(SniptError::Other(format!(
"Daemon failed to start. Check logs at {}",
daemon_log_file
)));
}
}
#[cfg(windows)]
{
use std::process::Command;
let current_exe = std::env::current_exe()?;
let daemon_log_file = format!("{}\\daemon_log.txt", get_config_dir().to_string_lossy());
let cmd = format!(
"START /B \"snipt Daemon\" \"{}\" daemon-worker > \"{}\" 2>&1",
current_exe.to_string_lossy(),
daemon_log_file
);
Command::new("cmd").arg("/C").arg(&cmd).status()?;
for _ in 0..20 {
thread::sleep(Duration::from_millis(100));
if is_daemon_running()?.is_some() {
break;
}
}
if let Some(pid) = is_daemon_running()? {
if verify_process_running(pid) {
println!("Daemon started successfully with PID {}.", pid);
} else {
return Err(SniptError::Other(format!(
"Daemon process failed to start. Check logs at {}",
daemon_log_file
)));
}
} else {
return Err(SniptError::Other(format!(
"Daemon failed to start. Check logs at {}",
daemon_log_file
)));
}
}
}
println!("Starting API server...");
let mut current_port = api_port;
for _ in 0..10 {
if port_is_available(current_port) {
break;
}
println!(
"Port {} is busy, trying {}...",
current_port,
current_port + 1
);
current_port += 1;
}
if let Err(e) = save_api_port(current_port) {
println!("Warning: Failed to save API port information: {}", e);
}
let current_exe = std::env::current_exe()?;
#[cfg(unix)]
{
use std::process::Command;
let log_file = format!("{}/api_server_log.txt", get_config_dir().to_string_lossy());
let cmd = format!(
"nohup \"{}\" serve --port {} > \"{}\" 2>&1 &",
current_exe.to_string_lossy(),
current_port,
log_file
);
Command::new("sh").arg("-c").arg(&cmd).status()?;
thread::sleep(Duration::from_secs(2));
if !port_is_available(current_port) {
println!("API server started on port {}.", current_port);
println!(
"You can access the server at: http://localhost:{}",
current_port
);
Ok(())
} else {
Err(SniptError::Other(format!(
"API server failed to start. Check log at {}",
log_file
)))
}
}
#[cfg(windows)]
{
use std::process::Command;
let log_file = format!("{}\\api_server_log.txt", get_config_dir().to_string_lossy());
let cmd = format!(
"START /B \"snipt API Server\" \"{}\" serve --port {} > \"{}\" 2>&1",
current_exe.to_string_lossy(),
current_port,
log_file
);
Command::new("cmd").arg("/C").arg(&cmd).status()?;
thread::sleep(Duration::from_secs(2));
if !port_is_available(current_port) {
println!("API server started on port {}.", current_port);
println!(
"You can access the server at: http://localhost:{}",
current_port
);
return Ok(());
} else {
return Err(SniptError::Other(format!(
"API server failed to start. Check log at {}",
log_file
)));
}
}
#[cfg(not(any(unix, windows)))]
{
return Err(SniptError::Other(
"Starting API server not supported on this platform".to_string(),
));
}
}
pub fn stop_daemon() -> Result<()> {
let pid_file = get_pid_file_path();
if !pid_file.exists() {
return Err(SniptError::DaemonNotRunning);
}
let pid_str = match fs::read_to_string(&pid_file) {
Ok(content) => content,
Err(e) => {
let _ = fs::remove_file(&pid_file);
return Err(SniptError::Other(format!("Failed to read PID file: {}", e)));
}
};
let pid = match pid_str.trim().parse::<u32>() {
Ok(pid) => pid,
Err(_) => {
let _ = fs::remove_file(&pid_file);
return Err(SniptError::InvalidPid);
}
};
println!("Attempting to stop daemon with PID {}...", pid);
let _ = stop_api_server();
if !verify_process_running(pid) {
println!("Process with PID {} is not running.", pid);
let _ = fs::remove_file(&pid_file);
return Ok(());
}
#[cfg(unix)]
{
let mut success = false;
if let Ok(status) = std::process::Command::new("kill")
.arg(pid.to_string())
.status()
{
if status.success() {
println!("Sent termination signal to daemon with PID {}", pid);
success = true;
}
}
if !success || verify_process_running(pid) {
thread::sleep(Duration::from_millis(500));
if verify_process_running(pid) {
println!("Daemon didn't terminate gracefully, using force kill...");
if let Ok(status) = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.status()
{
if status.success() {
println!("Force killed daemon with PID {}", pid);
success = true;
}
}
} else {
success = true; }
}
let _ = std::process::Command::new("pkill")
.args(["-P", &pid.to_string()])
.status();
if success {
let _ = fs::remove_file(&pid_file);
println!("Daemon stopped successfully.");
return Ok(());
}
}
#[cfg(windows)]
{
use std::process::Command;
let mut success = false;
if let Ok(status) = Command::new("taskkill")
.args(&["/PID", &pid.to_string()])
.status()
{
if status.success() {
println!("Sent termination signal to daemon with PID {}", pid);
success = true;
}
}
if !success || verify_process_running(pid) {
thread::sleep(Duration::from_millis(500));
if verify_process_running(pid) {
println!("Daemon didn't terminate gracefully, using force kill...");
if let Ok(status) = Command::new("taskkill")
.args(&["/F", "/T", "/PID", &pid.to_string()])
.status()
{
if status.success() {
println!("Force killed daemon with PID {}", pid);
success = true;
}
}
} else {
success = true; }
}
if success {
let _ = fs::remove_file(&pid_file);
println!("Daemon stopped successfully.");
return Ok(());
}
}
println!("WARNING: Failed to stop daemon process. PID file will be removed anyway.");
let _ = fs::remove_file(&pid_file);
Ok(())
}
pub fn daemon_status() -> Result<()> {
match is_daemon_running()? {
Some(pid) => {
let process_exists = verify_process_running(pid);
if process_exists {
println!("snipt daemon is running with PID {}", pid);
if let Ok(port) = get_api_server_port() {
println!("API server is running on port {}", port);
println!("UI available at: http://localhost:{}", port);
}
Ok(())
} else {
println!("PID file exists but process {} is not running", pid);
println!("This could indicate the daemon crashed or was stopped abruptly");
println!("Recommend running 'snipt stop' followed by 'snipt start'");
Ok(())
}
}
None => {
println!("snipt daemon is not running");
Ok(())
}
}
}
#[cfg(unix)]
pub fn daemon_worker() -> Result<()> {
let pid_file = get_pid_file_path();
let mut file = File::create(&pid_file)?;
write!(file, "{}", process::id())?;
run_daemon_worker()
}
pub fn run_daemon_worker() -> Result<()> {
let db_path = get_db_file_path();
if !db_path.exists() {
return Err(SniptError::DatabaseNotFound(
db_path.to_string_lossy().to_string(),
));
}
let snippets = Arc::new(Mutex::new(load_snippets()?));
let last_modified = Arc::new(Mutex::new(fs::metadata(&db_path)?.modified().ok()));
let running = Arc::new(Mutex::new(true));
let running_clone = Arc::clone(&running);
let keyboard_thread = start_keyboard_listener(Arc::clone(&snippets), running_clone);
let db_path_clone = db_path.clone();
let snippets_clone = Arc::clone(&snippets);
let last_modified_clone = Arc::clone(&last_modified);
let check_interval = Duration::from_secs(1);
while *running.lock().unwrap() {
thread::sleep(Duration::from_millis(100));
static mut LAST_CHECK: Option<std::time::Instant> = None;
let should_check = unsafe {
let now = std::time::Instant::now();
let check = match LAST_CHECK {
Some(last) => now.duration_since(last) >= check_interval,
None => true,
};
if check {
LAST_CHECK = Some(now);
}
check
};
if should_check {
if let Ok(metadata) = fs::metadata(&db_path_clone) {
if let Ok(current_modified) = metadata.modified() {
let reload_needed = {
let mut last_mod = last_modified_clone.lock().unwrap();
if let Some(last_mod_time) = *last_mod {
if current_modified > last_mod_time {
*last_mod = Some(current_modified);
true
} else {
false
}
} else {
*last_mod = Some(current_modified);
false
}
};
if reload_needed {
if let Ok(new_snippets) = load_snippets() {
let mut snippets_guard = snippets_clone.lock().unwrap();
*snippets_guard = new_snippets;
}
}
}
}
}
}
if let Err(e) = keyboard_thread.join() {
eprintln!("Error joining keyboard thread: {:?}", e);
}
Ok(())
}
pub fn daemon_worker_entry() -> Result<()> {
let pid_file = get_pid_file_path();
let mut file = File::create(&pid_file)?;
write!(file, "{}", process::id())?;
let result = run_daemon_worker();
let _ = fs::remove_file(&pid_file);
result
}