use std::path::Path;
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
use std::process::Command;
use std::sync::atomic::AtomicBool;
use super::{PreparedPush, PushError, PushReport};
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
use super::{PushFailure, protocol};
use crate::Signature;
use crate::config::ConfigInputs;
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
use crate::packet::Wire;
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
use crate::transport::Server;
use crate::transport::TransportControl;
pub fn send_local(
destination: impl AsRef<Path>,
prepared: &PreparedPush,
cancel: &AtomicBool,
) -> Result<PushReport, PushError> {
send_local_with_control(destination, prepared, TransportControl::new(cancel))
}
pub fn send_local_with_control(
destination: impl AsRef<Path>,
prepared: &PreparedPush,
control: TransportControl<'_>,
) -> Result<PushReport, PushError> {
let inputs = ConfigInputs::default();
send_local_with_context(
destination,
prepared,
control,
LocalPushContext {
config_inputs: &inputs,
identity: None,
},
)
}
#[non_exhaustive]
pub struct LocalPushContext<'a> {
pub config_inputs: &'a ConfigInputs,
pub identity: Option<&'a Signature>,
}
impl std::fmt::Debug for LocalPushContext<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LocalPushContext").finish_non_exhaustive()
}
}
impl<'a> LocalPushContext<'a> {
pub fn new(config_inputs: &'a ConfigInputs) -> Self {
Self {
config_inputs,
identity: None,
}
}
#[must_use]
pub fn with_config_inputs(mut self, config_inputs: &'a ConfigInputs) -> Self {
self.config_inputs = config_inputs;
self
}
#[must_use]
pub fn with_identity(mut self, identity: Option<&'a Signature>) -> Self {
self.identity = identity;
self
}
}
pub fn send_local_with_context(
destination: impl AsRef<Path>,
prepared: &PreparedPush,
control: TransportControl<'_>,
context: LocalPushContext<'_>,
) -> Result<PushReport, PushError> {
send_local_with_progress(destination, prepared, control, context, |_| {})
}
pub fn send_local_with_progress(
destination: impl AsRef<Path>,
prepared: &PreparedPush,
control: TransportControl<'_>,
context: LocalPushContext<'_>,
validation: impl FnMut(crate::fetch::ValidationProgress),
) -> Result<PushReport, PushError> {
send_local_preparing_observed(
destination.as_ref(),
prepared.format,
control,
context,
|_| Ok(prepared),
validation,
)
}
pub(crate) fn send_local_preparing_observed<P: std::borrow::Borrow<PreparedPush>>(
destination: &Path,
format: crate::ObjectFormat,
control: TransportControl<'_>,
context: LocalPushContext<'_>,
prepare: impl FnOnce(&super::local_native::LocalAdvertisement<'_>) -> Result<P, super::PushFailure>,
validation: impl FnMut(crate::fetch::ValidationProgress),
) -> Result<PushReport, PushError> {
#[cfg(feature = "tracing")]
let span = tracing::debug_span!(
target: "girt",
"push.local",
outcome = "incomplete",
failure_class = tracing::field::Empty,
effects = tracing::field::Empty,
accepted = tracing::field::Empty,
rejected = tracing::field::Empty,
pending = tracing::field::Empty,
unpack = tracing::field::Empty,
);
let operation = || {
super::local_native::send_preparing(
destination,
format,
control,
context.identity,
context.config_inputs,
prepare,
validation,
)
};
#[cfg(feature = "tracing")]
let result = span.in_scope(operation);
#[cfg(not(feature = "tracing"))]
let result = { operation }();
#[cfg(feature = "tracing")]
crate::trace::finish(&span, &result, |error| crate::trace::push(error, &span));
#[cfg(feature = "tracing")]
if let Ok(report) = &result {
crate::trace::push_report(&span, report);
}
result
}
pub fn send_local_with_identity(
destination: impl AsRef<Path>,
prepared: &PreparedPush,
control: TransportControl<'_>,
identity: &Signature,
) -> Result<PushReport, PushError> {
let inputs = ConfigInputs::default();
send_local_with_context(
destination,
prepared,
control,
LocalPushContext {
config_inputs: &inputs,
identity: Some(identity),
},
)
}
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
fn send_server(
command: &mut Command,
prepared: &PreparedPush,
control: TransportControl<'_>,
) -> Result<PushReport, PushError> {
#[cfg(feature = "tracing")]
let span = tracing::debug_span!(
target: "girt",
"push.local_server",
outcome = "incomplete",
failure_class = tracing::field::Empty,
effects = tracing::field::Empty,
accepted = tracing::field::Empty,
rejected = tracing::field::Empty,
pending = tracing::field::Empty,
unpack = tracing::field::Empty,
);
let operation = || {
let mut child =
Server::spawn(command, control).map_err(|e| PushError::NotSent(e.into()))?;
let (body, exchange, written, caps, request) = {
let (mut reader, writer) = child.streams();
let mut wire = Wire {
reader: &mut reader,
remaining: prepared.limits.max_advertisement_bytes,
cancel: control.cancel,
};
let caps = protocol::advertise(&mut wire, prepared).map_err(PushError::NotSent)?;
control.check().map_err(|e| PushError::NotSent(e.into()))?;
let request = prepared
.request_for(caps.report_v2, caps.sideband)
.map_err(PushError::NotSent)?;
let (body, exchange, written) = Server::exchange(
reader,
writer,
&request,
&prepared.pack.to_vec(),
prepared.limits.max_status_bytes,
);
(body, exchange, written, caps, request)
};
if written == 0
&& let Err(cause) = exchange
{
return Err(PushError::NotSent(cause.into()));
}
let mut report = PushReport::pending(&prepared.commands);
report.mark_attempted(&request, written);
let retain = AtomicBool::new(false);
let mut reader = body.as_slice();
let mut wire = Wire {
reader: &mut reader,
remaining: prepared.limits.max_status_bytes,
cancel: &retain,
};
let parsed = if prepared.commands.is_empty() {
wire.end().map_err(PushFailure::from)
} else {
protocol::read_response(
&mut wire,
&mut report,
caps,
prepared.limits.max_status_bytes,
)
.and_then(|()| wire.end().map_err(PushFailure::from))
};
let transfer = exchange.map_err(PushFailure::from).and(parsed);
if let Err(cause) = transfer {
return if prepared.commands.is_empty() {
Err(PushError::NotSent(cause))
} else {
Err(PushError::Uncertain {
cause,
report: Box::new(report),
})
};
}
let result = child.wait().map_err(PushFailure::from).and_then(|status| {
if status.success() {
Ok(())
} else {
Err(PushFailure::Process(status))
}
});
match result {
Ok(()) => Ok(report),
Err(cause) if prepared.commands.is_empty() => Err(PushError::NotSent(cause)),
Err(cause) => Err(PushError::Uncertain {
cause,
report: Box::new(report),
}),
}
};
#[cfg(feature = "tracing")]
let result = span.in_scope(operation);
#[cfg(not(feature = "tracing"))]
let result = { operation }();
#[cfg(feature = "tracing")]
crate::trace::finish(&span, &result, |error| crate::trace::push(error, &span));
#[cfg(feature = "tracing")]
if let Ok(report) = &result {
crate::trace::push_report(&span, report);
}
result
}
#[cfg(all(test, any(target_os = "macos", target_os = "linux")))]
mod tests;