liana/lianad/src/bin/cli.rs
2024-11-19 18:01:25 +01:00

177 lines
5.3 KiB
Rust

#![cfg(not(target_os = "windows"))]
use lianad::config::{config_folder_path, Config};
use std::{
env,
io::{Read, Write},
path::PathBuf,
process,
};
use serde_json::Value as Json;
use std::os::unix::net::UnixStream;
// Exits with error
fn show_usage() {
eprintln!("Usage:");
eprintln!(" liana-cli [--conf conf_path] [--raw] <command> [<param 1> <param 2> ...]");
process::exit(1);
}
// Returns (Maybe(special conf file), Raw, Method name, Maybe(List of parameters))
fn parse_args(mut args: Vec<String>) -> (Option<PathBuf>, bool, String, Vec<String>) {
if args.len() < 2 {
eprintln!("Not enough arguments.");
show_usage();
}
args.remove(0); // Program name
let mut args = args.into_iter();
let mut raw = false;
let mut conf_file = None;
loop {
match args.next().as_deref() {
Some("--conf") => {
if args.len() < 2 {
eprintln!("Not enough arguments.");
show_usage();
}
conf_file = Some(PathBuf::from(args.next().expect("Just checked")));
}
Some("--raw") => {
if args.len() < 1 {
eprintln!("Not enough arguments.");
show_usage();
}
raw = true;
}
Some(method) => return (conf_file, raw, method.to_owned(), args.collect()),
None => {
// Should never happen...
eprintln!("Not enough arguments.");
show_usage();
}
}
}
}
// Defaults to String Value when parsing fails, as it fails to parse outpoints otherwise...
fn from_str_hack(token: String) -> Json {
match serde_json::from_str(&token) {
Ok(json) => json,
Err(_) => Json::String(token),
}
}
fn rpc_request(method: String, params: Vec<String>) -> Json {
let method = Json::String(method);
let params = Json::Array(params.into_iter().map(from_str_hack).collect::<Vec<Json>>());
let mut object = serde_json::Map::<String, Json>::new();
object.insert("jsonrpc".to_string(), Json::String("2.0".to_string()));
object.insert(
"id".to_string(),
Json::String(format!("liana-cli-{}", process::id())),
);
object.insert("method".to_string(), method);
object.insert("params".to_string(), params);
Json::Object(object)
}
fn socket_file(conf_file: Option<PathBuf>) -> PathBuf {
let config = Config::from_file(conf_file).unwrap_or_else(|e| {
eprintln!("Error getting config: {}", e);
process::exit(1);
});
let data_dir = config
.data_dir
.unwrap_or_else(|| config_folder_path().unwrap());
let data_dir = data_dir.to_str().expect("Datadir is valid unicode");
[
data_dir,
config.bitcoin_config.network.to_string().as_str(),
"lianad_rpc",
]
.iter()
.collect()
}
fn trimmed(mut vec: Vec<u8>, bytes_read: usize) -> Vec<u8> {
vec.truncate(bytes_read);
// Until there is some whatever-newline character, pop.
while let Some(byte) = vec.last() {
// Of course, we assume utf-8
if !(&0x0a..=&0x0d).contains(&byte) {
break;
}
vec.pop();
}
vec
}
fn main() {
let args = env::args().collect();
let (conf_file, raw, method, params) = parse_args(args);
let request = rpc_request(method, params);
let socket_file = socket_file(conf_file);
let mut raw_response = vec![0; 256];
let mut socket = UnixStream::connect(&socket_file).unwrap_or_else(|e| {
eprintln!("Could not connect to {:?}: '{}'", socket_file, e);
process::exit(1);
});
socket
.write_all(&[request.to_string().as_bytes(), b"\n"].concat())
.unwrap_or_else(|e| {
eprintln!("Writing to {:?}: '{}'", &socket_file, e);
process::exit(1);
});
let mut total_read = 0;
loop {
let n = socket
.read(&mut raw_response[total_read..])
.unwrap_or_else(|e| {
eprintln!("Reading from {:?}: '{}'", &socket_file, e);
process::exit(1);
});
total_read += n;
if total_read == raw_response.len() {
raw_response.resize(2 * total_read, 0);
continue;
}
// FIXME: do actual incremental parsing instead of this hack!!
raw_response = trimmed(raw_response, total_read);
match serde_json::from_slice::<Json>(&raw_response) {
Ok(response) => {
if response.get("id") == request.get("id") {
if raw {
print!("{}", response);
} else if let Some(r) = response.get("result") {
println!("{:#}", serde_json::json!({ "result": r }));
} else if let Some(e) = response.get("error") {
println!("{:#}", serde_json::json!({ "error": e }));
} else {
log::warn!(
"lianad response doesn't contain result or error: '{}'",
response
);
println!("{:#}", response);
}
return;
}
}
Err(_) => continue,
}
}
}