use bstr::{BStr, BString}; use crate::{ driver, driver::{Operation, State, apply::handle_io_err}, }; /// pub mod list { use crate::driver; /// The error returned by [State::list_delayed_paths()][super::State::list_delayed_paths()]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error("Could not get process named '{}' which should be running and tracked", wanted.0)] ProcessMissing { wanted: driver::Key }, #[error("Failed to run 'list_available_blobs' command")] ProcessInvoke(#[from] driver::process::client::invoke::without_content::Error), #[error("The invoked command 'list_available_blobs' in process indicated an error: {status:?}")] ProcessStatus { status: driver::process::Status }, } } /// pub mod fetch { use crate::driver; /// The error returned by [State::fetch_delayed()][super::State::fetch_delayed()]. #[derive(Debug, thiserror::Error)] #[expect(missing_docs)] pub enum Error { #[error("Could not get process named '{}' which should be running and tracked", wanted.0)] ProcessMissing { wanted: driver::Key }, #[error("Failed to run '{command}' command")] ProcessInvoke { command: String, source: driver::process::client::invoke::Error, }, #[error("The invoked command '{command}' in process indicated an error: {status:?}")] ProcessStatus { status: driver::process::Status, command: String, }, } } /// Operations related to delayed filtering. impl State { /// Return a list of delayed paths for `process` that can then be obtained with [`fetch_delayed()`][Self::fetch_delayed()]. /// /// A process abiding the protocol will eventually list all previously delayed paths for any invoked command, or /// signals that it is done with all delayed paths by returning an empty list. /// It's up to the caller to validate these assumptions. /// /// ### Error Handling /// /// Usually if the process sends the "abort" status, we will not use a certain capability again. Here it's unclear what capability /// that is and what to do, so we leave the process running and do nothing else (just like `git`). pub fn list_delayed_paths(&mut self, process: &driver::Key) -> Result, list::Error> { let client = self .running .get_mut(&process.0) .ok_or_else(|| list::Error::ProcessMissing { wanted: process.clone(), })?; let mut out = Vec::new(); let result = client.invoke_without_content("list_available_blobs", &mut None.into_iter(), &mut |line| { if let Some(path) = line.strip_prefix(b"pathname=") { out.push(path.into()); } }); let status = match result { Ok(res) => res, Err(err) => { if let driver::process::client::invoke::without_content::Error::Io(err) = &err { handle_io_err(err, &mut self.running, process.0.as_ref()); } return Err(err.into()); } }; if status.is_success() { Ok(out) } else { let message = status.message().unwrap_or_default(); match message { "error" | "abort" => {} _strange => { let client = self.running.remove(&process.0).expect("we definitely have it"); client.into_child().kill().ok(); } } Err(list::Error::ProcessStatus { status }) } } /// Given a `process` and a `path` (as previously returned by [list_delayed_paths()][Self::list_delayed_paths()]), return /// a reader to stream the filtered result. Note that `operation` must match the original operation that produced the delayed result /// or the long-running process might not know the path, depending on its implementation. pub fn fetch_delayed( &mut self, process: &driver::Key, path: &BStr, operation: Operation, ) -> Result { let client = self .running .get_mut(&process.0) .ok_or_else(|| fetch::Error::ProcessMissing { wanted: process.clone(), })?; let result = client.invoke( operation.as_str(), &mut [("pathname", path.to_owned())].into_iter(), &mut &b""[..], ); let status = match result { Ok(status) => status, Err(err) => { let driver::process::client::invoke::Error::Io(io_err) = &err; handle_io_err(io_err, &mut self.running, process.0.as_ref()); return Err(fetch::Error::ProcessInvoke { command: operation.as_str().into(), source: err, }); } }; if status.is_success() { // TODO: find a way to not have to do the 'borrow-dance'. let client = self.running.remove(&process.0).expect("present for borrowcheck dance"); self.running.insert(process.0.clone(), client); let client = self.running.get_mut(&process.0).expect("just inserted"); Ok(client.as_read()) } else { let message = status.message().unwrap_or_default(); match message { "abort" => { client.capabilities_mut().remove(operation.as_str()); } "error" => {} _strange => { let client = self.running.remove(&process.0).expect("we definitely have it"); client.into_child().kill().ok(); } } Err(fetch::Error::ProcessStatus { command: operation.as_str().into(), status, }) } } }