Wherry/src/transfer/queue.rs
loki5512344 e188fbef36
feat: FTPS, AppError, Site Manager, tests, multi-window, auto-clear, CI/CD, virtual scroll
- FTPS: реализован FtpsClient через suppaftp + async-native-tls
- AppError: кастомный enum с Serialize вместо Result<T, String>
- Site Manager UI: создание/редактирование/удаление сайтов
- Тесты: 53 тестов (domain, storage, transfer, fs, remote)
- Multi-window: новое окно + сохранение геометрии
- Auto-clear: воркер чистит завершённые задачи по таймеру
- CI/CD: 3-ступенчатый пайплайн (lint→build→test) с кэшированием
- Virtual scroll: fileTable с рендером только видимых строк
- UI: исправлены close-кнопки, color-scheme для select, скриншоты в README
2026-07-11 00:04:13 +02:00

165 lines
4.5 KiB
Rust

use crate::domain::transfer::{TaskState, TransferTask};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
pub struct TransferQueue {
inner: Arc<Mutex<VecDeque<TransferTask>>>,
}
impl TransferQueue {
pub fn push(&self, task: TransferTask) {
self.inner.lock().unwrap().push_back(task);
}
pub fn pop(&self) -> Option<TransferTask> {
self.inner.lock().unwrap().pop_front()
}
pub fn get(&self, id: &str) -> Option<TransferTask> {
self.inner
.lock()
.unwrap()
.iter()
.find(|t| t.id == id)
.cloned()
}
pub fn all(&self) -> Vec<TransferTask> {
self.inner.lock().unwrap().iter().cloned().collect()
}
pub fn update_state(&self, id: &str, state: TaskState) {
let mut q = self.inner.lock().unwrap();
if let Some(t) = q.iter_mut().find(|t| t.id == id) {
t.state = state;
}
}
pub fn update_progress(&self, id: &str, transferred: u64, speed: u64) {
let mut q = self.inner.lock().unwrap();
if let Some(t) = q.iter_mut().find(|t| t.id == id) {
t.transferred_bytes = transferred;
t.speed = Some(speed);
let remaining = t.total_bytes.saturating_sub(transferred);
t.eta_secs = remaining.checked_div(speed);
}
}
pub fn remove(&self, id: &str) {
let mut q = self.inner.lock().unwrap();
q.retain(|t| t.id != id);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::transfer::{TaskState, TransferKind, TransferTask};
fn make_task(id: &str, total: u64) -> TransferTask {
TransferTask {
id: id.into(),
kind: TransferKind::Download,
connection_id: "conn-1".into(),
local_path: "/local".into(),
remote_path: "/remote".into(),
file_name: "file.txt".into(),
total_bytes: total,
transferred_bytes: 0,
state: TaskState::Queued,
speed: None,
eta_secs: None,
}
}
#[test]
fn test_push_pop_all() {
let q = TransferQueue::default();
assert!(q.all().is_empty());
q.push(make_task("t1", 100));
q.push(make_task("t2", 200));
assert_eq!(q.all().len(), 2);
let t1 = q.pop().unwrap();
assert_eq!(t1.id, "t1");
assert_eq!(q.all().len(), 1);
let t2 = q.pop().unwrap();
assert_eq!(t2.id, "t2");
assert!(q.pop().is_none());
}
#[test]
fn test_get() {
let q = TransferQueue::default();
q.push(make_task("t1", 100));
q.push(make_task("t2", 200));
assert_eq!(q.get("t2").unwrap().total_bytes, 200);
assert!(q.get("nonexistent").is_none());
}
#[test]
fn test_update_state() {
let q = TransferQueue::default();
q.push(make_task("t1", 100));
for state in [
TaskState::Running,
TaskState::Paused,
TaskState::Completed,
TaskState::Failed("err".into()),
TaskState::Cancelled,
TaskState::Retrying(5),
] {
q.update_state("t1", state.clone());
assert_eq!(q.get("t1").unwrap().state, state);
}
}
#[test]
fn test_update_progress() {
let q = TransferQueue::default();
q.push(make_task("t1", 1000));
q.update_progress("t1", 250, 50);
let t = q.get("t1").unwrap();
assert_eq!(t.transferred_bytes, 250);
assert_eq!(t.speed, Some(50));
assert_eq!(t.eta_secs, Some(15));
}
#[test]
fn test_update_progress_zero_speed() {
let q = TransferQueue::default();
q.push(make_task("t1", 1000));
q.update_progress("t1", 100, 0);
let t = q.get("t1").unwrap();
assert_eq!(t.transferred_bytes, 100);
assert_eq!(t.speed, Some(0));
assert_eq!(t.eta_secs, None);
}
#[test]
fn test_remove() {
let q = TransferQueue::default();
q.push(make_task("t1", 100));
q.push(make_task("t2", 200));
q.push(make_task("t3", 300));
q.remove("t2");
let ids: Vec<String> = q.all().into_iter().map(|t| t.id).collect();
assert_eq!(ids, vec!["t1", "t3"]);
}
#[test]
fn test_remove_nonexistent() {
let q = TransferQueue::default();
q.push(make_task("t1", 100));
q.remove("does-not-exist");
assert_eq!(q.all().len(), 1);
}
}