use bluer::{
Address, AddressType,
l2cap::{PSM_LE_DYN_START, SocketAddr, Stream},
};
use std::{
env,
fs::{OpenOptions, create_dir_all},
io::{Write, stdin},
path::PathBuf,
};
use tokio::io::{AsyncBufReadExt, AsyncReadExt};
use tokio::io::{AsyncWriteExt, BufStream};
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 args: Vec<_> = env::args().collect();
assert!(
args.len() >= 2,
"Specify target Bluetooth address as argument"
);
let last_arg = args.last().unwrap();
let target_addr: Address = last_arg
.strip_prefix("blat::")
.unwrap_or(last_arg)
.parse()
.unwrap();
let sa = SocketAddr::new(target_addr, AddressType::LePublic, PSM);
let stream = Stream::connect(sa).await.unwrap();
let mut bs = BufStream::new(stream);
bs.read_u8().await.unwrap();
let mut line = String::new();
loop {
line.clear();
stdin().read_line(&mut line).unwrap();
if line.is_empty() {
break;
}
let tokens = line
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.map(str::trim)
.collect::<Vec<_>>();
match tokens.as_slice() {
["capabilities"] => {
println!("fetch");
println!();
}
["list"] => {
bs.write_all("list\n".as_bytes()).await.unwrap();
bs.flush().await.unwrap();
let list = list_response(&mut bs).await;
for item in list {
println!("{item}");
}
println!();
}
["fetch", id, _name] => {
bs.write_all(format!("fetch {id}\n").as_bytes())
.await
.unwrap();
bs.flush().await.unwrap();
fetch_response(&mut bs).await;
println!();
}
_ => {}
}
}
bs.write_all("\n".as_bytes()).await.unwrap();
}
async fn list_response(bs: &mut BufStream<Stream>) -> Vec<String> {
let mut line = String::new();
let mut res = vec![];
loop {
line.clear();
bs.read_line(&mut line).await.unwrap();
if line.trim().is_empty() {
break;
}
res.push(line.trim().to_string());
}
res
}
async fn fetch_response(bs: &mut BufStream<Stream>) {
let dst = env::var("GIT_DIR").map(PathBuf::from).unwrap();
let mut line = String::new();
loop {
line.clear();
bs.read_line(&mut line).await.unwrap();
if line.trim().is_empty() {
break;
}
let tokens = line
.trim()
.split(' ')
.filter(|s| !s.is_empty())
.map(str::trim)
.collect::<Vec<_>>();
match tokens.as_slice() {
[id, size] => {
let mut buf = vec![0; size.parse().unwrap()];
let mut dst = dst.clone();
bs.read_exact(buf.as_mut_slice()).await.unwrap();
dst.push("objects");
dst.push(&id[..2]);
create_dir_all(&dst).unwrap();
dst.push(&id[2..]);
if dst.exists() {
return;
}
let mut f = OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(dst)
.unwrap();
f.write_all(buf.as_slice()).unwrap();
}
_ => panic!("Bad fetch response tokens"),
}
}
}