use ureq::BodyReader;
use crate::{
Command, Error, HashKind, PackDecoder, WithError, bail,
command::Execute,
parcel::{ParcelDecoder, ParcelItem},
};
#[derive(Default, Debug)]
pub struct Fetch {
pub want: Vec<String>,
pub have: Vec<String>,
pub filter: Option<String>,
pub deepen: Option<usize>,
pub hash_kind: HashKind,
pub include_tag: bool,
}
impl From<&Fetch> for Command {
fn from(value: &Fetch) -> Self {
let mut cmd = Command::new("fetch").object_format(value.hash_kind);
for want in &value.want {
cmd = cmd.arg(format!("want {want}"));
}
for have in &value.have {
cmd = cmd.arg(format!("have {have}"));
}
if let Some(filter) = &value.filter {
cmd = cmd.arg(format!("filter {filter}"));
}
if let Some(deepen) = &value.deepen {
cmd = cmd.arg(format!("deepen {deepen}"));
}
if value.include_tag {
cmd = cmd.arg("include-tag");
}
cmd = cmd.arg("done");
cmd
}
}
impl Execute for Fetch {
type Output = PackDecoder<FetchDecoder<BodyReader<'static>>>;
fn execute(&self, repo: &crate::Repo) -> Result<Self::Output, Error> {
let parcel = repo.stream(&self.into())?;
let fetch = FetchDecoder::new(parcel);
let pack = PackDecoder::new(fetch, self.hash_kind)?;
Ok(pack)
}
}
#[derive(PartialEq, Eq)]
enum FetchMode {
Initial,
Packfile,
ShallowInfo,
}
pub struct FetchDecoder<R: std::io::Read> {
mode: FetchMode,
pack_data: Vec<u8>,
reader: ParcelDecoder<R>,
}
impl<R: std::io::Read> FetchDecoder<R> {
fn new(reader: ParcelDecoder<R>) -> Self {
Self {
mode: FetchMode::Initial,
pack_data: vec![],
reader,
}
}
fn read_data(&mut self) -> Result<Vec<u8>, Error> {
let mut data = vec![];
while data.is_empty() {
let item = self.reader.get_one().with_err_msg("read one item");
if item == Ok(ParcelItem::End) {
return Ok(vec![]);
}
data = self.parse_data(item)?;
}
Ok(data)
}
fn parse_data(&mut self, item: Result<ParcelItem, Error>) -> Result<Vec<u8>, Error> {
match item? {
ParcelItem::Data(data) if self.mode == FetchMode::Initial => {
let header = str::from_utf8(&data).with_err_msg("could not decode header")?;
match header {
"packfile\n" => {
self.mode = FetchMode::Packfile;
Ok(vec![])
}
"shallow-info\n" => {
self.mode = FetchMode::ShallowInfo;
Ok(vec![])
}
hdr => {
bail!("unknown header: {hdr}");
}
}
}
ParcelItem::Data(_data) if self.mode == FetchMode::ShallowInfo => {
Ok(vec![])
}
ParcelItem::Data(data) if self.mode == FetchMode::Packfile => {
let stream = data.first().with_err_msg("stream empty")?;
let data = &data[1..];
match *stream {
1 => Ok(data.to_vec()),
2 => {
let msg = str::from_utf8(data)
.with_err_msg("unexpected non-utf8 data in status stream")?;
print!("{msg}");
Ok(vec![])
}
3 => {
let msg = str::from_utf8(data)
.with_err_msg("unexpected non-utf8 data in error stream")?;
bail!("remote error: {msg}");
}
n => {
bail!("unknown stream: {n}");
}
}
}
ParcelItem::Data(_data) => {
todo!()
}
ParcelItem::Separator => {
self.mode = FetchMode::Initial;
Ok(vec![])
}
ParcelItem::End => {
bail!("unexpected data end");
}
}
}
}
impl<R: std::io::Read> std::io::Read for FetchDecoder<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let data = if self.pack_data.is_empty() {
self.read_data()?
} else {
std::mem::take(&mut self.pack_data)
};
if data.is_empty() {
return Ok(0);
}
if data.len() > buf.len() {
let src = &data[0..buf.len()];
let rest = &data[buf.len()..];
buf.copy_from_slice(src);
self.pack_data.extend_from_slice(rest);
return Ok(buf.len());
}
let dst = &mut buf[0..data.len()];
dst.copy_from_slice(&data);
Ok(data.len())
}
}
impl<R: std::io::Read> std::io::BufRead for FetchDecoder<R> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
if self.pack_data.is_empty() {
let data = self.read_data()?;
self.pack_data.extend_from_slice(&data);
}
Ok(&self.pack_data)
}
fn consume(&mut self, amount: usize) {
self.pack_data.drain(0..amount);
}
}