git.delta.rocks / remowt / refs/commits / 6d9cf16dada2

difftreelog

source

cmds/remowt-agent/src/editor.rs2.9 KiBsourcehistory
1use std::env::{current_dir, temp_dir};2use std::path::Path;3use std::time::Duration;4use std::{fs, io};56use anyhow::{bail, Context as _};7use nix::libc;8use remowt_link_shared::editor::EditorEndpointsClient;9use tokio::process::Command;10use zbus::{fdo, interface, proxy, Connection};1112use remowt_link_shared::BifConfig;1314const BUS_NAME: &str = "lach.RemowtEditor";15const SERVICE_PATH: &str = "/lach/Editor";1617pub struct EditorService {18	editor: EditorEndpointsClient<BifConfig>,19}2021#[interface(name = "lach.RemowtEditor")]22impl EditorService {23	/// Attach the User's GUI to the nvim server at `socket_path` (on the remote),24	/// blocking until the user is done.25	async fn edit(&self, socket_path: String) -> fdo::Result<()> {26		self.editor27			.open_editor(socket_path)28			.await29			.map_err(|e| fdo::Error::Failed(format!("requesting editor on the User: {e}")))?30			.map_err(|e| fdo::Error::Failed(format!("editor failed: {e}")))?;31		Ok(())32	}33}3435pub async fn serve(36	conn: &Connection,37	editor: EditorEndpointsClient<BifConfig>,38) -> anyhow::Result<()> {39	conn.object_server()40		.at(SERVICE_PATH, EditorService { editor })41		.await?;42	conn.request_name(BUS_NAME).await?;43	Ok(())44}4546#[proxy(interface = "lach.RemowtEditor")]47trait RemowtEditor {48	async fn edit(&self, socket_path: &str) -> fdo::Result<()>;49}5051pub async fn edit(path: String) -> anyhow::Result<()> {52	let path = Path::new(&path);53	let abs = if path.is_absolute() {54		path.to_path_buf()55	} else {56		current_dir()?.join(path)57	};5859	let sock = temp_dir().join(format!("remowt-nvim-{}.sock", uuid::Uuid::new_v4()));60	let sock_str = sock61		.to_str()62		.context("temp socket path is not utf-8")?63		.to_owned();6465	let mut child = Command::new("nvim");66	child67		.arg("--headless")68		.arg("--listen")69		.arg(&sock)70		.arg("--")71		.arg(&abs)72		.kill_on_drop(true);73	// SAFETY: only an async-signal-safe `prctl` call.74	unsafe {75		child.pre_exec(|| {76			if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as libc::c_ulong) != 0 {77				return Err(io::Error::last_os_error());78			}79			Ok(())80		});81	}82	let mut child = child.spawn().context("spawning nvim")?;8384	wait_for_socket(&sock)85		.await86		.context("nvim did not start its server")?;8788	let conn = Connection::session()89		.await90		.context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;91	let proxy = RemowtEditorProxy::builder(&conn)92		.destination(BUS_NAME)?93		.path(SERVICE_PATH)?94		.build()95		.await?;96	let result = proxy.edit(&sock_str).await;9798	if tokio::time::timeout(Duration::from_secs(2), child.wait())99		.await100		.is_err()101	{102		let _ = child.kill().await;103	}104	let _ = fs::remove_file(&sock);105106	result?;107	Ok(())108}109110/// Poll for `path` to appear (nvim creating its listen socket), up to ~10s.111async fn wait_for_socket(path: &Path) -> anyhow::Result<()> {112	for _ in 0..200 {113		if tokio::fs::try_exists(path).await.unwrap_or(false) {114			return Ok(());115		}116		tokio::time::sleep(Duration::from_millis(50)).await;117	}118	bail!("timed out waiting for {}", path.display())119}