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(&self, user: Option<String>, host: Option<String>, path: impl Into<String>) -> Self::Endpoint {
59        RsyncEndpoint { user, host, path: path.into() }
60    }
61}
62
63impl RsyncEndpointViewAlg for RsyncSyntax {
64    fn endpoint_user<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str> {
65        endpoint.user.as_deref()
66    }
67
68    fn endpoint_host<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str> {
69        endpoint.host.as_deref()
70    }
71
72    fn endpoint_path<'a>(&self, endpoint: &'a Self::Endpoint) -> &'a str {
73        &endpoint.path
74    }
75}
76
77impl SyncCommandAlg for RsyncSyntax {
78    fn sync_command(&self, program: impl Into<String>, arguments: impl IntoIterator<Item = String>) -> Self::Command {
79        SyncCommand { program: program.into(), arguments: arguments.into_iter().collect() }
80    }
81}
82
83impl SyncCommandViewAlg for RsyncSyntax {
84    fn command_program<'a>(&self, command: &'a Self::Command) -> &'a str {
85        &command.program
86    }
87
88    fn command_arguments<'a>(&self, command: &'a Self::Command) -> impl Iterator<Item = &'a str> {
89        command.arguments.iter().map(String::as_str)
90    }
91}
92
93impl SyncChangeAlg for RsyncSyntax {
94    fn sync_change(&self, path: impl Into<String>, kind: ChangeKind, size: Option<u64>) -> Self::Change {
95        PlannedChange::new(path.into(), kind, size)
96    }
97}
98
99impl SyncObservationAlg for RsyncSyntax {
100    fn observed_change(&self, change: Self::Change) -> Self::Observation {
101        SyncObservation::Change(change)
102    }
103
104    fn observed_progress(&self, transferred: u64, percent: u16) -> Self::Observation {
105        SyncObservation::Progress { transferred, percent }
106    }
107
108    fn observed_nothing(&self) -> Self::Observation {
109        SyncObservation::Nothing
110    }
111}
112
113impl SyncObservationViewAlg for RsyncSyntax {
114    fn observation_change<'a>(&self, observation: &'a Self::Observation) -> Option<&'a Self::Change> {
115        match observation {
116            SyncObservation::Change(change) => Some(change),
117            SyncObservation::Progress { .. } | SyncObservation::Nothing => None,
118        }
119    }
120
121    fn observation_progress(&self, observation: &Self::Observation) -> Option<(u64, u16)> {
122        match observation {
123            SyncObservation::Progress { transferred, percent } => Some((*transferred, *percent)),
124            SyncObservation::Change(_) | SyncObservation::Nothing => None,
125        }
126    }
127}
128
129/// Runs folder transfers by stating a transcript instead of moving anything.
130// Several capabilities delegate to the same component, which Clippy reads as a repeated attribute.
131#[allow(clippy::duplicated_attributes, reason = "one delegation per capability, not per target")]
132#[derive(Clone, Debug, Default, Delegate)]
133#[delegate(RsyncEndpointAlg, target = "syntax")]
134#[delegate(RsyncEndpointViewAlg, target = "syntax")]
135#[delegate(SyncCommandAlg, target = "syntax")]
136#[delegate(SyncCommandViewAlg, target = "syntax")]
137#[delegate(SyncChangeAlg, target = "syntax")]
138#[delegate(SyncObservationAlg, target = "syntax")]
139#[delegate(SyncObservationViewAlg, target = "syntax")]
140pub struct ReferenceSyncEnv {
141    syntax: RsyncSyntax,
142    transcript: Vec<String>,
143    refusal: Option<String>,
144    commands: RefCell<Vec<SyncCommand>>,
145    watched: RefCell<Vec<SyncObservation>>,
146}
147
148/// Denotes a transfer the reference interpreter refused to run.
149#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
150#[error("the reference transfer refused: {0}")]
151pub struct ReferenceSyncError(pub String);
152
153impl RsyncSorts for ReferenceSyncEnv {
154    type Endpoint = RsyncEndpoint;
155    type Command = SyncCommand;
156    type Observation = SyncObservation;
157    type Change = PlannedChange;
158}
159
160impl ReferenceSyncEnv {
161    /// States exactly what the next transfer writes, whatever command it is given.
162    pub fn register_transcript(&mut self, lines: impl IntoIterator<Item = String>) {
163        self.transcript = lines.into_iter().collect();
164    }
165
166    /// Refuses every subsequent transfer, so failure laws can be exercised.
167    pub fn refuse_transfers(&mut self, reason: impl Into<String>) {
168        self.refusal = Some(reason.into());
169    }
170
171    /// Observes every command the interpreter was asked to run, in order.
172    #[must_use]
173    pub fn commands(&self) -> Vec<SyncCommand> {
174        self.commands.borrow().clone()
175    }
176
177    /// Observes everything the interpreter read out of its transfers, in order.
178    #[must_use]
179    pub fn watched(&self) -> Vec<SyncObservation> {
180        self.watched.borrow().clone()
181    }
182}
183
184impl SyncRunAlg for ReferenceSyncEnv {
185    type Error = ReferenceSyncError;
186    type Run = VecDeque<String>;
187
188    fn start_sync(&self, command: &Self::Command) -> Result<Self::Run, Self::Error> {
189        self.commands.borrow_mut().push(command.clone());
190        match &self.refusal {
191            Some(reason) => Err(ReferenceSyncError(reason.clone())),
192            None => Ok(self.transcript.iter().cloned().collect()),
193        }
194    }
195
196    fn next_sync_line(&self, run: &mut Self::Run) -> Result<Option<String>, Self::Error> {
197        Ok(run.pop_front())
198    }
199
200    fn finish_sync(&self, _run: Self::Run) -> Result<(), Self::Error> {
201        Ok(())
202    }
203}
204
205impl SyncWatchAlg for ReferenceSyncEnv {
206    fn watch_sync(&self, observation: &Self::Observation) {
207        self.watched.borrow_mut().push(observation.clone());
208    }
209}