use std::path::PathBuf;
use anyhow::{Context as _, Result};
use super::{Cli, Command};
use crate::caller;
use crate::client::{Client, Response};
use crate::paths::Paths;
use crate::spawn;
#[must_use]
pub fn resolve_paths(home_override: Option<&PathBuf>) -> Paths {
match home_override {
Some(p) => Paths::at(p.clone()),
None => Paths::home(),
}
}
pub fn resolve_session(cli: &Cli, paths: &Paths) -> Result<Option<String>> {
if let Some(s) = &cli.session {
return Ok(Some(s.clone()));
}
if let Ok(s) = std::env::var("VS_SESSION") {
let trimmed = s.trim();
if !trimmed.is_empty() {
return Ok(Some(trimmed.to_string()));
}
}
if let Some(key) = caller::caller_key() {
let p = paths.caller_session(&key);
if let Ok(contents) = std::fs::read_to_string(&p) {
let trimmed = contents.trim();
if !trimmed.is_empty() {
return Ok(Some(trimmed.to_string()));
}
}
}
Ok(None)
}
pub fn connect(cli: &Cli, paths: &Paths) -> Result<Client> {
let socket = cli.socket.clone().unwrap_or_else(|| paths.socket());
if !vs_daemon::transport::is_listening(&socket) && !cli.no_spawn {
let mut extra: Vec<String> = Vec::new();
if let Some(home) = cli.home.as_ref() {
extra.push(format!("--home={}", home.display()));
}
let extra_refs: Vec<&str> = extra.iter().map(String::as_str).collect();
spawn::spawn_daemon(&extra_refs)?;
spawn::wait_for_socket(&socket, std::time::Duration::from_secs(2))?;
}
Client::connect(&socket)
}
fn save_caller_session(paths: &Paths, key: &str, session_id: &str) -> Result<()> {
let path = paths.caller_session(key);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).context("create callers/ directory")?;
}
std::fs::write(&path, session_id).context("write caller session file")?;
Ok(())
}
const CLOSED_SENTINEL: &str = "__closed__";
fn mark_caller_closed(paths: &Paths, key: &str) -> Result<()> {
save_caller_session(paths, key, CLOSED_SENTINEL)
}
pub fn run(cli: &Cli) -> Result<Response> {
let paths = resolve_paths(cli.home.as_ref());
let mut session_id = resolve_session(cli, &paths)?;
let mut client = connect(cli, &paths)?;
let caller_key = caller::caller_key();
let explicit_close = matches!(session_id.as_deref(), Some(CLOSED_SENTINEL));
if explicit_close {
session_id = None;
}
if session_id.is_none()
&& !explicit_close
&& cli.command.needs_session()
&& !matches!(cli.command, Command::SessionOpen { .. })
{
let open_req = vs_protocol::Request::new("vs_session_open");
let open_resp = client.call(&open_req).context("auto session-open")?;
if let vs_protocol::Envelope::Success(_) = &open_resp.envelope {
if let Some(line) = open_resp.body.first() {
let id = line.trim().to_string();
if let Some(key) = caller_key.as_ref() {
let _ = save_caller_session(&paths, key, &id);
}
session_id = Some(id);
}
}
}
match &cli.command {
Command::PromptInput {
page,
r,
message,
secret,
token,
group,
} => {
let value = read_user_input(message, *secret)?;
let mut req = vs_protocol::Request::new("vs_act")
.arg(page.clone())
.arg(r.to_string())
.arg("fill".to_string())
.arg(value)
.flag_value("session", session_id.clone().unwrap_or_default())
.flag_value("token", token.clone());
if let Some(g) = group {
req = req.flag_value("group", g.clone());
}
return client.call(&req).context("daemon call");
}
Command::PromptConfirm { page: _, message } => {
read_user_confirm(message)?;
return Ok(Response {
envelope: vs_protocol::Envelope::Success(vs_protocol::StateToken([0u8; 8])),
body: Vec::new(),
warnings: Vec::new(),
});
}
_ => {}
}
let req = cli.command.to_request(session_id.as_deref())?;
let resp = client.call(&req).context("daemon call")?;
match (&cli.command, &resp.envelope) {
(Command::SessionOpen { .. }, vs_protocol::Envelope::Success(_)) => {
if let (Some(line), Some(key)) = (resp.body.first(), caller_key.as_ref()) {
let _ = save_caller_session(&paths, key, line.trim());
}
}
(Command::SessionClose, vs_protocol::Envelope::Success(_)) => {
if let Some(key) = caller_key.as_ref() {
let _ = mark_caller_closed(&paths, key);
}
}
_ => {}
}
Ok(resp)
}
fn read_user_input(message: &str, secret: bool) -> Result<String> {
use std::io::Write as _;
let mut stderr = std::io::stderr();
if secret {
let v = rpassword::prompt_password(format!("{message} ")).context("read secret")?;
Ok(v)
} else {
write!(stderr, "{message} ").ok();
stderr.flush().ok();
let mut buf = String::new();
std::io::stdin().read_line(&mut buf).context("read line")?;
Ok(buf.trim_end_matches(['\r', '\n']).to_string())
}
}
fn read_user_confirm(message: &str) -> Result<()> {
use std::io::Write as _;
let mut stderr = std::io::stderr();
write!(stderr, "{message} [Enter to confirm, Ctrl-C to abort] ").ok();
stderr.flush().ok();
let mut buf = String::new();
let n = std::io::stdin()
.read_line(&mut buf)
.context("read confirm")?;
if n == 0 {
anyhow::bail!("ABORTED: stdin closed before confirm");
}
Ok(())
}