1use crate::spec_registry::FtpSpecRegistry;
2use crate::storage::MockForgeStorage;
3use crate::vfs::VirtualFileSystem;
4use anyhow::Result;
5use libunftp::ServerBuilder;
6use mockforge_core::config::FtpConfig;
7use std::sync::Arc;
8
9#[derive(Debug)]
11pub struct FtpServer {
12 config: FtpConfig,
13 vfs: Arc<VirtualFileSystem>,
14 spec_registry: Arc<FtpSpecRegistry>,
15}
16
17impl FtpServer {
18 pub fn new(config: FtpConfig) -> Self {
19 let vfs = Arc::new(VirtualFileSystem::new(config.virtual_root.clone()));
20 let spec_registry = Arc::new(FtpSpecRegistry::new().with_vfs(vfs.clone()));
21
22 Self {
23 config,
24 vfs,
25 spec_registry,
26 }
27 }
28
29 pub async fn start(&self) -> Result<()> {
30 let addr = format!("{}:{}", self.config.host, self.config.port);
31 println!("Starting FTP server on {}", addr);
32
33 let storage = MockForgeStorage::new(self.vfs.clone(), self.spec_registry.clone());
35
36 let server = ServerBuilder::new(Box::new(move || storage.clone()))
38 .greeting("MockForge FTP Server")
39 .passive_ports(49152..=65534); println!("FTP server listening on {}", addr);
42 let server = server.build()?;
43 server.listen(&addr).await?;
44
45 Ok(())
46 }
47
48 pub async fn handle_upload(&self, path: &std::path::Path, data: Vec<u8>) -> Result<()> {
49 let path_str = path.to_string_lossy();
51
52 if let Some(rule) = self.spec_registry.find_upload_rule(&path_str) {
54 rule.validate_file(&data, &path_str).map_err(|e| anyhow::anyhow!(e))?;
56
57 if rule.auto_accept {
58 match &rule.storage {
60 crate::fixtures::UploadStorage::Memory => {
61 let size = data.len() as u64;
63 let file = crate::vfs::VirtualFile::new(
64 path.to_path_buf(),
65 crate::vfs::FileContent::Static(data),
66 crate::vfs::FileMetadata {
67 size,
68 ..Default::default()
69 },
70 );
71 self.vfs.add_file(path.to_path_buf(), file)?;
72 }
73 crate::fixtures::UploadStorage::File { path: storage_path } => {
74 tokio::fs::write(storage_path, &data).await?;
76 }
77 crate::fixtures::UploadStorage::Discard => {
78 }
80 }
81 }
82 }
83
84 Ok(())
85 }
86
87 pub fn spec_registry(&self) -> Arc<FtpSpecRegistry> {
88 self.spec_registry.clone()
89 }
90
91 pub fn vfs(&self) -> Arc<VirtualFileSystem> {
92 self.vfs.clone()
93 }
94}