Skip to main content

rsynko_memory/
sync.rs

1//! The in-memory layout of folder transfers, and a run that states a transcript.
2
3use crate::PlannedChange;
4use ambassador::Delegate;
5use rsynko_manager::ChangeKind;
6use rsynko_rsync::*;
7use std::cell::RefCell;
8use std::collections::VecDeque;
9
10/// Interprets the folder-transfer sorts as ordinary in-memory records.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub struct RsyncSyntax;
13
14/// Denotes one end of a folder transfer.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct RsyncEndpoint {
17    /// Names the account the far end is reached as.
18    pub user: Option<String>,
19    /// Names the machine the path rests on.
20    pub host: Option<String>,
21    /// Names the path itself.
22    pub path: String,
23}
24
25/// Denotes exactly the command one transfer runs.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct SyncCommand {
28    /// Names the program the transfer is performed by.
29    pub program: String,
30    /// States every argument, in order.
31    pub arguments: Vec<String>,
32}
33
34/// Denotes one thing a running transfer states.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub enum SyncObservation {
37    /// States one path the transfer would change, or has changed.
38    Change(PlannedChange),
39    /// States how far the whole transfer has come.
40    Progress {
41        /// Counts the bytes moved so far.
42        transferred: u64,
43        /// States the completed share as whole percent.
44        percent: u16,
45    },
46    /// States a line naming nothing the specification reads.
47    Nothing,
48}
49
50impl RsyncSorts for RsyncSyntax {
51    type Endpoint = RsyncEndpoint;
52    type Command = SyncCommand;
53    type Observation = SyncObservation;
54    type Change = PlannedChange;
55}
56
57impl RsyncEndpointAlg for RsyncSyntax {
58    fn endpoint(
59        &self,
60        user: Option<String>,
61        host: Option<String>,
62        path: impl Into<String>,
63    ) -> Self::Endpoint {
64        RsyncEndpoint {
65            user,
66            host,
67            path: path.into(),
68        }
69    }
70}
71
72impl RsyncEndpointViewAlg for RsyncSyntax {
73    fn endpoint_user<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str> {
74        endpoint.user.as_deref()
75    }
76
77    fn endpoint_host<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str> {
78        endpoint.host.as_deref()
79    }
80
81    fn endpoint_path<'a>(&self, endpoint: &'a Self::Endpoint) -> &'a str {
82        &endpoint.path
83    }
84}
85
86impl SyncCommandAlg for RsyncSyntax {
87    fn sync_command(
88        &self,
89        program: impl Into<String>,
90        arguments: impl IntoIterator<Item = String>,
91    ) -> Self::Command {
92        SyncCommand {
93            program: program.into(),
94            arguments: arguments.into_iter().collect(),
95        }
96    }
97}
98
99impl SyncCommandViewAlg for RsyncSyntax {
100    fn command_program<'a>(&self, command: &'a Self::Command) -> &'a str {
101        &command.program
102    }
103
104    fn command_arguments<'a>(&self, command: &'a Self::Command) -> impl Iterator<Item = &'a str> {
105        command.arguments.iter().map(String::as_str)
106    }
107}
108
109impl SyncChangeAlg for RsyncSyntax {
110    fn sync_change(
111        &self,
112        path: impl Into<String>,
113        kind: ChangeKind,
114        size: Option<u64>,
115    ) -> Self::Change {
116        PlannedChange::new(path.into(), kind, size)
117    }
118}
119
120impl SyncObservationAlg for RsyncSyntax {
121    fn observed_change(&self, change: Self::Change) -> Self::Observation {
122        SyncObservation::Change(change)
123    }
124
125    fn observed_progress(&self, transferred: u64, percent: u16) -> Self::Observation {
126        SyncObservation::Progress {
127            transferred,
128            percent,
129        }
130    }
131
132    fn observed_nothing(&self) -> Self::Observation {
133        SyncObservation::Nothing
134    }
135}
136
137impl SyncObservationViewAlg for RsyncSyntax {
138    fn observation_change<'a>(
139        &self,
140        observation: &'a Self::Observation,
141    ) -> Option<&'a Self::Change> {
142        match observation {
143            SyncObservation::Change(change) => Some(change),
144            SyncObservation::Progress { .. } | SyncObservation::Nothing => None,
145        }
146    }
147
148    fn observation_progress(&self, observation: &Self::Observation) -> Option<(u64, u16)> {
149        match observation {
150            SyncObservation::Progress {
151                transferred,
152                percent,
153            } => Some((*transferred, *percent)),
154            SyncObservation::Change(_) | SyncObservation::Nothing => None,
155        }
156    }
157}
158
159/// Runs folder transfers by stating a transcript instead of moving anything.
160// Several capabilities delegate to the same component, which Clippy reads as a repeated attribute.
161#[allow(
162    clippy::duplicated_attributes,
163    reason = "one delegation per capability, not per target"
164)]
165#[derive(Clone, Debug, Default, Delegate)]
166#[delegate(RsyncEndpointAlg, target = "syntax")]
167#[delegate(RsyncEndpointViewAlg, target = "syntax")]
168#[delegate(SyncCommandAlg, target = "syntax")]
169#[delegate(SyncCommandViewAlg, target = "syntax")]
170#[delegate(SyncChangeAlg, target = "syntax")]
171#[delegate(SyncObservationAlg, target = "syntax")]
172#[delegate(SyncObservationViewAlg, target = "syntax")]
173pub struct ReferenceSyncEnv {
174    syntax: RsyncSyntax,
175    transcript: Vec<String>,
176    refusal: Option<String>,
177    commands: RefCell<Vec<SyncCommand>>,
178    watched: RefCell<Vec<SyncObservation>>,
179}
180
181/// Denotes a transfer the reference interpreter refused to run.
182#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
183#[error("the reference transfer refused: {0}")]
184pub struct ReferenceSyncError(pub String);
185
186impl RsyncSorts for ReferenceSyncEnv {
187    type Endpoint = RsyncEndpoint;
188    type Command = SyncCommand;
189    type Observation = SyncObservation;
190    type Change = PlannedChange;
191}
192
193impl ReferenceSyncEnv {
194    /// States exactly what the next transfer writes, whatever command it is given.
195    pub fn register_transcript(&mut self, lines: impl IntoIterator<Item = String>) {
196        self.transcript = lines.into_iter().collect();
197    }
198
199    /// Refuses every subsequent transfer, so failure laws can be exercised.
200    pub fn refuse_transfers(&mut self, reason: impl Into<String>) {
201        self.refusal = Some(reason.into());
202    }
203
204    /// Observes every command the interpreter was asked to run, in order.
205    #[must_use]
206    pub fn commands(&self) -> Vec<SyncCommand> {
207        self.commands.borrow().clone()
208    }
209
210    /// Observes everything the interpreter read out of its transfers, in order.
211    #[must_use]
212    pub fn watched(&self) -> Vec<SyncObservation> {
213        self.watched.borrow().clone()
214    }
215}
216
217impl SyncRunAlg for ReferenceSyncEnv {
218    type Error = ReferenceSyncError;
219    type Run = VecDeque<String>;
220
221    fn start_sync(&self, command: &Self::Command) -> Result<Self::Run, Self::Error> {
222        self.commands.borrow_mut().push(command.clone());
223        match &self.refusal {
224            Some(reason) => Err(ReferenceSyncError(reason.clone())),
225            None => Ok(self.transcript.iter().cloned().collect()),
226        }
227    }
228
229    fn next_sync_line(&self, run: &mut Self::Run) -> Result<Option<String>, Self::Error> {
230        Ok(run.pop_front())
231    }
232
233    fn finish_sync(&self, _run: Self::Run) -> Result<(), Self::Error> {
234        Ok(())
235    }
236}
237
238impl SyncWatchAlg for ReferenceSyncEnv {
239    fn watch_sync(&self, observation: &Self::Observation) {
240        self.watched.borrow_mut().push(observation.clone());
241    }
242}