use std::str::FromStr;
use anyhow::Context;
use clap::Parser;
use iroh::{Endpoint, EndpointAddr, SecretKey};
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, stdin};
use tracing::debug;
use crate::{GIT_IROH_ALPN, GitOp, keys, parse_iroh_url};
#[derive(Parser, Debug)]
#[command()]
pub struct ClientArgs {
remote_name: String,
url: String,
#[arg(env = "GIT_DIR")]
git_dir: String,
}
pub struct Client {
args: ClientArgs,
key: SecretKey,
}
impl Client {
pub fn new(args: ClientArgs) -> Result<Self, anyhow::Error> {
let key = keys::load_or_create_key(&args.git_dir)?;
Ok(Self { args, key })
}
pub async fn run<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
self,
input: R,
mut output: W,
) -> Result<(), anyhow::Error> {
let bufr = BufReader::new(input);
let mut lines = bufr.lines();
loop {
let line = match lines.next_line().await? {
Some(line) => line,
None => break,
};
let command: Vec<&str> = line.split_whitespace().collect();
if command.is_empty() {
continue;
}
debug!(?command, "client");
match command[0] {
"capabilities" => self.capabilities(&mut output).await?,
"connect" => {
if command.len() < 2 {
anyhow::bail!("connect: missing <url>");
}
self.connect(&mut output, command[1]).await?
}
s => anyhow::bail!("unsupported command: {:?}", s),
}
output.write(b"\n").await?;
}
Ok(())
}
async fn capabilities<W: AsyncWrite + Unpin>(
&self,
output: &mut W,
) -> Result<(), anyhow::Error> {
output.write(b"connect\n").await?;
Ok(())
}
async fn connect<W: AsyncWrite + Unpin>(
&self,
output: &mut W,
cmd: &str,
) -> Result<(), anyhow::Error> {
let remote_node_id = parse_iroh_url(self.args.url.as_str())?;
let remote_op = GitOp::from_str(cmd)?;
let ep = Endpoint::builder(iroh::endpoint::presets::N0)
.secret_key(self.key.clone())
.bind()
.await?;
ep.online().await;
let addr = EndpointAddr::new(remote_node_id);
let conn = ep.connect(addr, GIT_IROH_ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await?;
send.write_u8(remote_op.to_byte()).await?;
println!();
let mut stdin = stdin();
let (r1, r2) = tokio::join!(
tokio::io::copy(&mut stdin, &mut send),
tokio::io::copy(&mut recv, output),
);
r1.context("stdin -> send")?;
r2.context("recv -> stdout")?;
ep.close().await;
Ok(())
}
}