use bluer::{
Uuid,
adv::Advertisement,
l2cap::{PSM_LE_DYN_START, SocketAddr, Stream, StreamListener},
};
use std::{env::current_dir, error::Error, fs::File, io::Read, process::Command};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufStream};
const SERVICE_UUID: Uuid = Uuid::from_u128(0x18c2_12e8_657d_4228_be09_ce01_bcc0_3e13);
const PSM: u16 = PSM_LE_DYN_START + 5;
#[tokio::main]
async fn main() {
let session = bluer::Session::new().await.unwrap();
let adapter = session.default_adapter().await.unwrap();
adapter.set_powered(true).await.unwrap();
let adapter_addr = adapter.address().await.unwrap();
let adapter_addr_type = adapter.address_type().await.unwrap();
let le_advertisement = Advertisement {
service_uuids: vec![SERVICE_UUID].into_iter().collect(),
discoverable: Some(true),
local_name: Some("blat".to_string()),
..Default::default()
};
let _adv_handle = adapter.advertise(le_advertisement).await.unwrap();
let local_sa = SocketAddr::new(adapter_addr, adapter_addr_type, PSM);
let mut listener = StreamListener::bind(local_sa).await.unwrap();
loop {
eprintln!(
"Waiting for connection, remote url: 'blat::{}'",
local_sa.addr
);
if let Err(e) = handle_connection(&mut listener).await {
eprintln!("{e}");
}
}
}
async fn handle_connection(listener: &mut StreamListener) -> Result<(), Box<dyn Error>> {
let (stream, _) = listener.accept().await?;
let mut bs = BufStream::new(stream);
bs.write_u8(255).await?;
bs.flush().await?;
eprintln!("Accepted connection.");
let mut line = String::new();
loop {
line.clear();
bs.read_line(&mut line).await?;
if line.is_empty() {
return Ok(());
}
let tokens = line
.split(' ')
.filter(|s| !s.is_empty())
.map(str::trim)
.collect::<Vec<_>>();
eprintln!("Received: {tokens:?}");
match tokens.as_slice() {
["list"] => list_refs(&mut bs).await?,
["fetch", id] => write_fetch(&mut bs, id).await?,
_ => return Ok(()),
}
}
}
async fn list_refs(bs: &mut BufStream<Stream>) -> Result<(), Box<dyn Error>> {
let out = Command::new("git")
.arg("show-ref")
.current_dir(current_dir()?)
.output()?
.stdout;
bs.write_all(out.as_slice()).await?;
bs.write_all("\n".as_bytes()).await?;
bs.flush().await?;
Ok(())
}
async fn write_fetch(bs: &mut BufStream<Stream>, id: &str) -> Result<(), Box<dyn Error>> {
let cmdout = &Command::new("git")
.arg("rev-list")
.arg("--objects")
.arg(id)
.current_dir(current_dir()?)
.output()?
.stdout;
let cmdoutstr = str::from_utf8(cmdout)?;
eprintln!("Expanded {id} to: {cmdoutstr}\n");
let ids = cmdoutstr
.split('\n')
.map(str::trim)
.collect::<Vec<_>>();
for id in ids {
if id.trim().is_empty() {
continue;
}
let id = id.split(' ').next().unwrap();
let mut src = current_dir()?.as_path().to_path_buf();
src.push(".git");
src.push("objects");
src.push(&id[..2]);
src.push(&id[2..]);
if !src.exists() {
return Ok(());
}
let mut buf = vec![];
let src = src.as_path();
File::open(src)?.read_to_end(&mut buf)?;
eprintln!("Sending fetch of {id} {} bytes...", buf.len());
bs.write_all(format!("{id} {}\n", buf.len()).as_bytes())
.await?;
bs.flush().await?;
bs.write_all(buf.as_slice()).await?;
bs.flush().await?;
eprintln!("Sent fetch of {id} {} bytes", buf.len());
}
bs.write_all("\n".as_bytes()).await?;
bs.flush().await?;
Ok(())
}