virtual_file_transfer/
transfer.rs1use crate::cancellation::CancellationToken;
2
3use super::virtual_file::VirtualFile;
4
5use std::sync::{Arc, Mutex};
6
7#[non_exhaustive]
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum TransferEffect {
15 None,
17 Copy,
19 Move,
21 Link,
23 Other(u32),
25}
26
27#[non_exhaustive]
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum NativeStatusCode {
35 WindowsHResult(i32),
39 }
41
42#[non_exhaustive]
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub enum TransferStatus {
50 Pending,
52 Cancelled,
54 Dropped,
59 AsyncFinished {
64 succeeded: bool,
66 native_code: NativeStatusCode,
68 effect: TransferEffect,
70 },
71 Failed,
73}
74
75pub struct TransferRequest {
81 pub(crate) files: Vec<VirtualFile>,
82 pub(crate) cancellation: CancellationToken,
83}
84
85impl TransferRequest {
86 pub fn new(files: Vec<VirtualFile>) -> Self {
92 Self {
93 files,
94 cancellation: CancellationToken::new(),
95 }
96 }
97 pub fn cancellation_token(&self) -> CancellationToken {
102 self.cancellation.clone()
103 }
104}
105
106impl From<Vec<VirtualFile>> for TransferRequest {
107 fn from(files: Vec<VirtualFile>) -> Self {
108 Self::new(files)
109 }
110}
111
112#[derive(Clone, Copy, Debug)]
113enum CompletionState {
114 Pending,
115 ReadAborted,
116 Finished(TransferStatus),
117}
118
119pub(crate) struct TransferCompletion {
120 state: Mutex<CompletionState>,
121}
122
123impl TransferCompletion {
124 pub(crate) fn new() -> Self {
125 TransferCompletion {
126 state: Mutex::new(CompletionState::Pending),
127 }
128 }
129
130 pub(crate) fn mark_as_read_aborted(&self) -> std::result::Result<(), ()> {
131 let mut state = self.state.lock().map_err(|_| ())?;
132 if matches!(*state, CompletionState::Pending) {
133 *state = CompletionState::ReadAborted;
134 }
135 Ok(())
136 }
137
138 pub(crate) fn record_if_pending(
139 &self,
140 new_status: TransferStatus,
141 ) -> std::result::Result<(), ()> {
142 let mut state = self.state.lock().map_err(|_| ())?;
143 *state = match *state {
144 CompletionState::Pending => CompletionState::Finished(new_status),
145
146 CompletionState::ReadAborted => CompletionState::Finished(TransferStatus::Cancelled),
147
148 CompletionState::Finished(_) => return Ok(()),
149 };
150 Ok(())
151 }
152
153 pub(crate) fn status(&self) -> TransferStatus {
154 let state = self.state.lock().unwrap_or_else(|error| error.into_inner());
155
156 match *state {
157 CompletionState::Pending | CompletionState::ReadAborted => TransferStatus::Pending,
158 CompletionState::Finished(status) => status,
159 }
160 }
161}
162
163pub struct TransferHandle {
168 completion: Arc<TransferCompletion>,
169 cancellation: CancellationToken,
170}
171
172impl TransferHandle {
173 pub(crate) fn new(completion: Arc<TransferCompletion>, token: CancellationToken) -> Self {
174 Self {
175 completion,
176 cancellation: token,
177 }
178 }
179
180 pub fn status(&self) -> TransferStatus {
185 self.completion.status()
186 }
187
188 pub fn cancel(&self) {
193 self.cancellation.cancel()
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use std::sync::Arc;
201
202 #[test]
203 fn initial_status_is_pending() {
204 let completion = Arc::new(TransferCompletion::new());
205 let handle = TransferHandle::new(completion, CancellationToken::new());
206
207 assert_eq!(handle.status(), TransferStatus::Pending);
208 }
209
210 #[test]
211 fn completion_updates_handle_status() {
212 let completion = Arc::new(TransferCompletion::new());
213 let handle = TransferHandle::new(Arc::clone(&completion), CancellationToken::new());
214
215 _ = completion.record_if_pending(TransferStatus::Dropped);
216
217 assert_eq!(handle.status(), TransferStatus::Dropped);
218 }
219
220 #[test]
221 fn completed_status_cannot_be_overwritten() {
222 let completion = TransferCompletion::new();
223
224 _ = completion.record_if_pending(TransferStatus::Cancelled);
225 _ = completion.record_if_pending(TransferStatus::Dropped);
226
227 assert_eq!(completion.status(), TransferStatus::Cancelled);
228 }
229
230 #[test]
231 fn completion_is_shared_across_threads() {
232 let completion = Arc::new(TransferCompletion::new());
233 let handle = TransferHandle::new(Arc::clone(&completion), CancellationToken::new());
234
235 let worker = std::thread::spawn(move || {
236 _ = completion.record_if_pending(TransferStatus::Dropped);
237 });
238
239 worker.join().unwrap();
240
241 assert_eq!(handle.status(), TransferStatus::Dropped);
242 }
243
244 #[test]
245 fn request_starts_not_cancelled() {
246 let request = TransferRequest::new(vec![]);
247
248 assert!(!request.cancellation_token().is_cancelled());
249 }
250
251 #[test]
252 fn request_preserves_cancellation_after_move() {
253 let request = TransferRequest::new(vec![]);
254 let token = request.cancellation_token();
255
256 let TransferRequest {
257 files,
258 cancellation,
259 } = request;
260
261 assert!(files.is_empty());
262 assert!(!cancellation.is_cancelled());
263
264 token.cancel();
265
266 assert!(cancellation.is_cancelled());
267 }
268
269 #[test]
270 fn request_can_be_created_from_files() {
271 let files: Vec<VirtualFile> = vec![];
272
273 let request: TransferRequest = files.into();
274
275 assert!(request.files.is_empty());
276 assert!(!request.cancellation_token().is_cancelled());
277 }
278}