use std::{
fs::{self, File},
io::Write,
os::unix::net::UnixStream,
path::PathBuf,
sync::mpsc,
};
use kak_tree_sitter_config::Config;
use tokio::{io::AsyncReadExt, net::UnixListener, select};
use crate::{
handler::Handler,
request::{KakTreeSitterOrigin, KakouneOrigin, Request},
response::Response,
};
#[derive(Debug)]
pub struct Daemon {
config: Config,
daemon_dir: PathBuf,
unix_listener: UnixListener,
}
impl Daemon {
fn new(config: Config, daemon_dir: PathBuf) -> Self {
let unix_listener = UnixListener::bind(daemon_dir.join("socket")).unwrap();
Self {
config,
daemon_dir,
unix_listener,
}
}
fn daemon_dir() -> PathBuf {
let dir = dirs::runtime_dir()
.or_else(||
std::env::var("TMPDIR").map(PathBuf::from).ok())
.unwrap(); dir.join("kak-tree-sitter")
}
pub fn bootstrap(config: Config, daemonize: bool) {
let daemon_dir = Self::daemon_dir();
fs::create_dir_all(&daemon_dir).unwrap(); eprintln!("running in {}", daemon_dir.display());
let pid_file = daemon_dir.join("pid");
if let Ok(true) = pid_file.try_exists() {
eprintln!("kak-tree-sitter already running; exiting");
return;
}
if daemonize {
let stdout_path = daemon_dir.join("stdout.txt");
let stderr_path = daemon_dir.join("stderr.txt");
let stdout = File::create(stdout_path).unwrap();
let stderr = File::create(stderr_path).unwrap();
daemonize::Daemonize::new()
.stdout(stdout)
.stderr(stderr)
.pid_file(pid_file)
.start()
.expect("daemon");
} else {
fs::write(pid_file, format!("{}", std::process::id())).unwrap(); }
let async_rt = tokio::runtime::Runtime::new().unwrap(); async_rt.block_on(async {
let daemon = Daemon::new(config, daemon_dir);
daemon.run().await;
});
}
async fn run(self) {
let mut req_handler = Handler::new(&self.config);
let (req_sx, req_rx) = mpsc::channel();
let (shutdown_sx, mut shutdown_rx) = tokio::sync::mpsc::unbounded_channel();
let handler_handle = tokio::task::spawn_blocking(move || {
for req in req_rx {
let resp = req_handler.handle_request(req);
if let Some((mut session, resp)) = resp {
if let Response::Shutdown = resp {
shutdown_sx.send(()).unwrap(); break;
}
session.send_response(&resp);
}
}
});
loop {
select! {
_ = shutdown_rx.recv() => break,
Ok((mut client, _)) = self.unix_listener.accept() => {
println!("client connected: {client:?}");
let mut req_str = String::new();
client.read_to_string(&mut req_str).await.unwrap();
let req = serde_json::from_str::<Request<KakTreeSitterOrigin>>(&req_str).unwrap();
req_sx.send(req).unwrap();
}
}
}
handler_handle.await.unwrap(); println!("bye!");
}
pub fn send_request(req: Request<KakouneOrigin>) {
let kts_req = req.reinterpret();
let serialized = serde_json::to_string(&kts_req).unwrap();
UnixStream::connect(Self::daemon_dir().join("socket"))
.unwrap() .write_all(serialized.as_bytes())
.unwrap(); }
}
impl Drop for Daemon {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.daemon_dir);
}
}