Wherry/src/protocols/ftp/transfer.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

107 lines
3.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Чанкованная загрузка/выгрузка по FTP с колбэком прогресса (pause/cancel).
use anyhow::Result;
use futures_lite::io::{AsyncReadExt, AsyncWriteExt};
use suppaftp::{AsyncFtpStream, AsyncNativeTlsFtpStream};
use tokio::fs::File;
use tokio::io::{AsyncReadExt as TokioReadExt, AsyncWriteExt as TokioWriteExt};
use crate::protocols::ProgressAction;
const CHUNK_SIZE: usize = 64 * 1024;
type ProgressCb = Option<Box<dyn Fn(u64) -> ProgressAction + Send>>;
fn check(cb: &ProgressCb, total: u64) -> Result<()> {
if let Some(cb) = cb {
match cb(total) {
ProgressAction::Continue => {}
ProgressAction::Cancel => return Err(anyhow::anyhow!("cancelled")),
ProgressAction::Pause => return Err(anyhow::anyhow!("paused")),
}
}
Ok(())
}
macro_rules! gen_transfer {
($upload:ident, $download:ident, $stream:ty) => {
pub(super) async fn $upload(
conn: &mut $stream,
local: &str,
remote: &str,
on_progress: ProgressCb,
) -> Result<()> {
let mut file = File::open(local)
.await
.map_err(|e| anyhow::anyhow!("cannot open local file: {}", e))?;
let mut dstream = conn
.put_with_stream(remote)
.await
.map_err(|e| anyhow::anyhow!("FTP upload stream failed: {}", e))?;
let mut buf = vec![0u8; CHUNK_SIZE];
let mut total = 0u64;
loop {
let n = file
.read(&mut buf)
.await
.map_err(|e| anyhow::anyhow!("FTP upload read failed: {}", e))?;
if n == 0 {
break;
}
dstream
.write_all(&buf[..n])
.await
.map_err(|e| anyhow::anyhow!("FTP upload write failed: {}", e))?;
total += n as u64;
check(&on_progress, total)?;
}
conn.finalize_put_stream(dstream)
.await
.map_err(|e| anyhow::anyhow!("FTP upload finalize failed: {}", e))?;
Ok(())
}
pub(super) async fn $download(
conn: &mut $stream,
remote: &str,
local: &str,
on_progress: ProgressCb,
) -> Result<()> {
let mut dstream = conn
.retr_as_stream(remote)
.await
.map_err(|e| anyhow::anyhow!("FTP download stream failed: {}", e))?;
let mut file = File::create(local)
.await
.map_err(|e| anyhow::anyhow!("cannot create local file: {}", e))?;
let mut buf = vec![0u8; CHUNK_SIZE];
let mut total = 0u64;
loop {
let n = dstream
.read(&mut buf)
.await
.map_err(|e| anyhow::anyhow!("FTP download read failed: {}", e))?;
if n == 0 {
break;
}
file.write_all(&buf[..n])
.await
.map_err(|e| anyhow::anyhow!("FTP download write failed: {}", e))?;
total += n as u64;
check(&on_progress, total)?;
}
conn.finalize_retr_stream(dstream)
.await
.map_err(|e| anyhow::anyhow!("FTP download finalize failed: {}", e))?;
Ok(())
}
};
}
gen_transfer!(upload_ftp, download_ftp, AsyncFtpStream);
gen_transfer!(upload_ftps, download_ftps, AsyncNativeTlsFtpStream);