Skip to main content

oxihuman_core/
file_transfer_stub.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! File transfer protocol stub.
6
7/// Transfer state.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum TransferState {
10    Pending,
11    InProgress { bytes_sent: u64, total_bytes: u64 },
12    Completed,
13    Failed(String),
14}
15
16impl TransferState {
17    pub fn is_done(&self) -> bool {
18        matches!(self, TransferState::Completed | TransferState::Failed(_))
19    }
20
21    pub fn progress_pct(&self) -> f32 {
22        match self {
23            TransferState::InProgress {
24                bytes_sent,
25                total_bytes,
26            } => {
27                if *total_bytes == 0 {
28                    1.0
29                } else {
30                    *bytes_sent as f32 / *total_bytes as f32
31                }
32            }
33            TransferState::Completed => 1.0,
34            _ => 0.0,
35        }
36    }
37}
38
39/// A file transfer job.
40#[derive(Debug, Clone)]
41pub struct TransferJob {
42    pub id: u64,
43    pub source: String,
44    pub destination: String,
45    pub state: TransferState,
46}
47
48impl TransferJob {
49    pub fn new(id: u64, source: &str, destination: &str, size_bytes: u64) -> Self {
50        TransferJob {
51            id,
52            source: source.to_string(),
53            destination: destination.to_string(),
54            state: TransferState::InProgress {
55                bytes_sent: 0,
56                total_bytes: size_bytes,
57            },
58        }
59    }
60
61    pub fn mark_complete(&mut self) {
62        self.state = TransferState::Completed;
63    }
64
65    pub fn mark_failed(&mut self, reason: &str) {
66        self.state = TransferState::Failed(reason.to_string());
67    }
68
69    pub fn advance(&mut self, bytes: u64) {
70        if let TransferState::InProgress {
71            ref mut bytes_sent,
72            total_bytes,
73        } = self.state
74        {
75            *bytes_sent = (*bytes_sent + bytes).min(total_bytes);
76            if *bytes_sent == total_bytes {
77                self.state = TransferState::Completed;
78            }
79        }
80    }
81}
82
83/// Transfer manager stub.
84pub struct TransferManager {
85    jobs: Vec<TransferJob>,
86    next_id: u64,
87}
88
89impl TransferManager {
90    pub fn new() -> Self {
91        TransferManager {
92            jobs: Vec::new(),
93            next_id: 1,
94        }
95    }
96
97    pub fn enqueue(&mut self, source: &str, destination: &str, size_bytes: u64) -> u64 {
98        let id = self.next_id;
99        self.next_id += 1;
100        self.jobs
101            .push(TransferJob::new(id, source, destination, size_bytes));
102        id
103    }
104
105    pub fn get_job(&self, id: u64) -> Option<&TransferJob> {
106        self.jobs.iter().find(|j| j.id == id)
107    }
108
109    pub fn get_job_mut(&mut self, id: u64) -> Option<&mut TransferJob> {
110        self.jobs.iter_mut().find(|j| j.id == id)
111    }
112
113    pub fn completed_count(&self) -> usize {
114        self.jobs
115            .iter()
116            .filter(|j| j.state == TransferState::Completed)
117            .count()
118    }
119
120    pub fn pending_count(&self) -> usize {
121        self.jobs.iter().filter(|j| !j.state.is_done()).count()
122    }
123}
124
125impl Default for TransferManager {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131/// Create a transfer manager.
132pub fn new_transfer_manager() -> TransferManager {
133    TransferManager::new()
134}
135
136/// Advance all in-progress jobs by `chunk_size` bytes.
137pub fn tick_all(mgr: &mut TransferManager, chunk_size: u64) {
138    for job in &mut mgr.jobs {
139        job.advance(chunk_size);
140    }
141}
142
143/// Cancel a job by ID (mark as failed).
144pub fn cancel_job(mgr: &mut TransferManager, id: u64) {
145    if let Some(job) = mgr.get_job_mut(id) {
146        job.mark_failed("cancelled");
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_enqueue_job() {
156        let mut m = new_transfer_manager();
157        let id = m.enqueue("/src", "/dst", 1000);
158        assert_eq!(id, 1);
159        assert!(m.get_job(id).is_some());
160    }
161
162    #[test]
163    fn test_advance_completes() {
164        let mut m = new_transfer_manager();
165        let id = m.enqueue("/src", "/dst", 100);
166        if let Some(job) = m.get_job_mut(id) {
167            job.advance(100);
168        }
169        assert_eq!(m.completed_count(), 1);
170    }
171
172    #[test]
173    fn test_progress_pct() {
174        let state = TransferState::InProgress {
175            bytes_sent: 50,
176            total_bytes: 100,
177        };
178        assert!((state.progress_pct() - 0.5).abs() < 0.01);
179    }
180
181    #[test]
182    fn test_is_done_completed() {
183        assert!(TransferState::Completed.is_done());
184    }
185
186    #[test]
187    fn test_is_done_failed() {
188        assert!(TransferState::Failed("err".to_string()).is_done());
189    }
190
191    #[test]
192    fn test_cancel_job() {
193        let mut m = new_transfer_manager();
194        let id = m.enqueue("/src", "/dst", 1000);
195        cancel_job(&mut m, id);
196        assert!(matches!(
197            m.get_job(id).expect("should succeed").state,
198            TransferState::Failed(_)
199        ));
200    }
201
202    #[test]
203    fn test_tick_all() {
204        let mut m = new_transfer_manager();
205        m.enqueue("/s", "/d", 100);
206        tick_all(&mut m, 50);
207        let j = m.get_job(1).expect("should succeed");
208        assert!((j.state.progress_pct() - 0.5).abs() < 0.01);
209    }
210
211    #[test]
212    fn test_pending_count() {
213        let mut m = new_transfer_manager();
214        m.enqueue("/s1", "/d1", 100);
215        m.enqueue("/s2", "/d2", 200);
216        assert_eq!(m.pending_count(), 2);
217    }
218
219    #[test]
220    fn test_mark_failed() {
221        let mut job = TransferJob::new(1, "/s", "/d", 100);
222        job.mark_failed("network error");
223        assert!(matches!(job.state, TransferState::Failed(_)));
224    }
225
226    #[test]
227    fn test_mark_complete() {
228        let mut job = TransferJob::new(1, "/s", "/d", 0);
229        job.mark_complete();
230        assert_eq!(job.state, TransferState::Completed);
231    }
232}