1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11use super::location::LocationId;
12use super::retry::RetryPolicy;
13use super::transfer::{Transfer, TransferKind, TransferState};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20pub enum PresenceState {
21 Present,
23 Pending,
25 Syncing,
27 Failed,
29 Absent,
31}
32
33impl PresenceState {
34 pub fn as_str(&self) -> &'static str {
35 match self {
36 Self::Present => "present",
37 Self::Pending => "pending",
38 Self::Syncing => "syncing",
39 Self::Failed => "failed",
40 Self::Absent => "absent",
41 }
42 }
43
44 pub fn priority(&self) -> u8 {
49 match self {
50 Self::Absent => 0,
51 Self::Present => 1,
52 Self::Pending => 2,
53 Self::Syncing => 3,
54 Self::Failed => 4,
55 }
56 }
57
58 pub fn from_transfer(transfer: &Transfer, policy: &RetryPolicy) -> Self {
60 match transfer.state() {
61 TransferState::Blocked => Self::Pending,
62 TransferState::Queued => Self::Pending,
63 TransferState::InFlight => Self::Syncing,
64 TransferState::Completed => Self::Present,
65 TransferState::Failed => {
66 if transfer.is_retryable(policy) {
67 Self::Pending
68 } else {
69 Self::Failed
70 }
71 }
72 TransferState::Cancelled => Self::Absent,
73 }
74 }
75}
76
77impl fmt::Display for PresenceState {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(self.as_str())
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PresenceView {
86 pub location: LocationId,
87 pub state: PresenceState,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub error: Option<String>,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub synced_at: Option<DateTime<Utc>>,
92 pub attempt: u32,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct ErrorEntry {
98 pub file_id: String,
99 pub src: LocationId,
100 pub dest: LocationId,
101 pub error: String,
102 pub attempts: u32,
103}
104
105impl ErrorEntry {
106 pub(crate) fn from_transfer(t: &Transfer) -> Self {
107 Self {
108 file_id: t.file_id().to_string(),
109 src: t.src().clone(),
110 dest: t.dest().clone(),
111 error: t.error().unwrap_or("unknown error").to_string(),
112 attempts: t.attempt(),
113 }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct PendingEntry {
120 pub file_id: String,
121 pub src: LocationId,
122 pub dest: LocationId,
123 pub kind: TransferKind,
124}
125
126impl PendingEntry {
127 pub(crate) fn from_transfer(t: &Transfer) -> Self {
128 Self {
129 file_id: t.file_id().to_string(),
130 src: t.src().clone(),
131 dest: t.dest().clone(),
132 kind: t.kind(),
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::domain::retry::TransferErrorKind;
141
142 fn loc(s: &str) -> LocationId {
143 LocationId::new(s).unwrap()
144 }
145
146 fn make_transfer(
147 state: TransferState,
148 error_kind: Option<TransferErrorKind>,
149 attempt: u32,
150 ) -> Transfer {
151 Transfer::reconstitute(
152 "t-1".into(),
153 "f-1".into(),
154 loc("local"),
155 loc("cloud"),
156 TransferKind::Sync,
157 state,
158 if state == TransferState::Failed {
159 Some("err".into())
160 } else {
161 None
162 },
163 error_kind,
164 attempt,
165 Utc::now(),
166 if state != TransferState::Queued {
167 Some(Utc::now())
168 } else {
169 None
170 },
171 if matches!(state, TransferState::Completed | TransferState::Failed) {
172 Some(Utc::now())
173 } else {
174 None
175 },
176 )
177 }
178
179 #[test]
180 fn presence_from_queued() {
181 let t = make_transfer(TransferState::Queued, None, 1);
182 assert_eq!(
183 PresenceState::from_transfer(&t, &RetryPolicy::default()),
184 PresenceState::Pending
185 );
186 }
187
188 #[test]
189 fn presence_from_completed() {
190 let t = make_transfer(TransferState::Completed, None, 1);
191 assert_eq!(
192 PresenceState::from_transfer(&t, &RetryPolicy::default()),
193 PresenceState::Present
194 );
195 }
196
197 #[test]
198 fn presence_from_in_flight() {
199 let t = make_transfer(TransferState::InFlight, None, 1);
200 assert_eq!(
201 PresenceState::from_transfer(&t, &RetryPolicy::default()),
202 PresenceState::Syncing
203 );
204 }
205
206 #[test]
207 fn presence_from_failed_transient_retryable() {
208 let policy = RetryPolicy::new(3);
209 let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Transient), 1);
210 assert_eq!(
211 PresenceState::from_transfer(&t, &policy),
212 PresenceState::Pending
213 );
214 }
215
216 #[test]
217 fn presence_from_failed_transient_exhausted() {
218 let policy = RetryPolicy::new(3);
219 let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Transient), 3);
220 assert_eq!(
221 PresenceState::from_transfer(&t, &policy),
222 PresenceState::Failed
223 );
224 }
225
226 #[test]
227 fn presence_from_failed_permanent() {
228 let policy = RetryPolicy::new(10);
229 let t = make_transfer(TransferState::Failed, Some(TransferErrorKind::Permanent), 1);
230 assert_eq!(
231 PresenceState::from_transfer(&t, &policy),
232 PresenceState::Failed
233 );
234 }
235}