1use crate::ProcessHold;
2use ambassador::Delegate;
3use rsynko_manager::ChangeKind;
4use rsynko_memory::{PlannedChange, RsyncEndpoint, RsyncSyntax, SyncCommand, SyncObservation};
5use rsynko_rsync::*;
6use std::io::{BufReader, Read};
7use std::process::{Child, ChildStdout, Command, Stdio};
8use std::sync::mpsc::Sender;
9
10#[allow(
13 clippy::duplicated_attributes,
14 reason = "one delegation per capability, not per target"
15)]
16#[derive(Debug, Delegate)]
17#[delegate(RsyncEndpointAlg, target = "syntax")]
18#[delegate(RsyncEndpointViewAlg, target = "syntax")]
19#[delegate(SyncCommandAlg, target = "syntax")]
20#[delegate(SyncCommandViewAlg, target = "syntax")]
21#[delegate(SyncChangeAlg, target = "syntax")]
22#[delegate(SyncObservationAlg, target = "syntax")]
23#[delegate(SyncObservationViewAlg, target = "syntax")]
24pub struct ProcessSyncEnv {
25 syntax: RsyncSyntax,
26 observations: Sender<SyncObservation>,
27 hold: ProcessHold,
28}
29
30impl RsyncSorts for ProcessSyncEnv {
31 type Endpoint = RsyncEndpoint;
32 type Command = SyncCommand;
33 type Observation = SyncObservation;
34 type Change = PlannedChange;
35}
36
37#[derive(Debug)]
39pub struct SyncRun {
40 child: Child,
41 output: BufReader<ChildStdout>,
42}
43
44#[derive(Debug, thiserror::Error)]
46pub enum ProcessSyncError {
47 #[error("the transfer program could not be started: {0}")]
49 Unstartable(std::io::Error),
50 #[error("the transfer could not be read: {0}")]
52 Unreadable(std::io::Error),
53 #[error("the transfer refused: {0}")]
55 Refused(String),
56}
57
58impl ProcessSyncEnv {
59 #[must_use]
61 pub fn new(observations: Sender<SyncObservation>) -> Self {
62 Self::held(observations, ProcessHold::default())
63 }
64
65 #[must_use]
67 pub const fn held(observations: Sender<SyncObservation>, hold: ProcessHold) -> Self {
68 Self {
69 syntax: RsyncSyntax,
70 observations,
71 hold,
72 }
73 }
74}
75
76impl SyncRunAlg for ProcessSyncEnv {
77 type Error = ProcessSyncError;
78 type Run = SyncRun;
79
80 fn start_sync(&self, command: &Self::Command) -> Result<Self::Run, Self::Error> {
81 let mut started = Command::new(self.command_program(command));
82 started
83 .args(self.command_arguments(command).collect::<Vec<_>>())
84 .stdin(Stdio::null())
85 .stdout(Stdio::piped())
86 .stderr(Stdio::piped());
87 #[cfg(unix)]
90 std::os::unix::process::CommandExt::process_group(&mut started, 0);
91 let mut child = started.spawn().map_err(ProcessSyncError::Unstartable)?;
92 self.hold.running(Some(child.id()));
93 let stdout = child
94 .stdout
95 .take()
96 .ok_or_else(|| ProcessSyncError::Refused("the transfer wrote nothing".to_owned()))?;
97 Ok(SyncRun {
98 child,
99 output: BufReader::new(stdout),
100 })
101 }
102
103 fn next_sync_line(&self, run: &mut Self::Run) -> Result<Option<String>, Self::Error> {
104 let mut line = Vec::new();
105 let mut byte = [0_u8; 1];
106 loop {
107 let read = run
108 .output
109 .read(&mut byte)
110 .map_err(ProcessSyncError::Unreadable)?;
111 if read == 0 {
112 break;
113 }
114 if byte[0] == b'\n' || byte[0] == b'\r' {
116 if line.is_empty() {
117 continue;
118 }
119 break;
120 }
121 line.push(byte[0]);
122 }
123 if line.is_empty() {
124 return Ok(None);
125 }
126 Ok(Some(String::from_utf8_lossy(&line).into_owned()))
127 }
128
129 fn finish_sync(&self, mut run: Self::Run) -> Result<(), Self::Error> {
130 let status = run.child.wait().map_err(ProcessSyncError::Unreadable)?;
131 self.hold.running(None);
132 if status.success() {
133 return Ok(());
134 }
135 let mut refusal = String::new();
136 if let Some(errors) = run.child.stderr.as_mut() {
137 let _read = errors.read_to_string(&mut refusal);
138 }
139 let refusal = refusal.trim();
140 Err(ProcessSyncError::Refused(if refusal.is_empty() {
141 format!("the transfer ended with {status}")
142 } else {
143 refusal.lines().take(4).collect::<Vec<_>>().join("; ")
144 }))
145 }
146}
147
148impl SyncWatchAlg for ProcessSyncEnv {
149 fn watch_sync(&self, observation: &Self::Observation) {
150 let _watched = self.observations.send(observation.clone());
153 }
154}