mockforge_ftp/
server.rs

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/// FTP Server implementation for MockForge
10#[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        // Create the storage backend
34        let storage = MockForgeStorage::new(self.vfs.clone(), self.spec_registry.clone());
35
36        // Create the FTP server with our custom storage
37        let server = ServerBuilder::new(Box::new(move || storage.clone()))
38            .greeting("MockForge FTP Server")
39            .passive_ports(49152..=65534); // Use dynamic port range for passive mode
40
41        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        // Handle file upload through fixtures
50        let path_str = path.to_string_lossy();
51
52        // Find matching upload rule
53        if let Some(rule) = self.spec_registry.find_upload_rule(&path_str) {
54            // Validate the upload
55            rule.validate_file(&data, &path_str).map_err(|e| anyhow::anyhow!(e))?;
56
57            if rule.auto_accept {
58                // Store the file based on rule
59                match &rule.storage {
60                    crate::fixtures::UploadStorage::Memory => {
61                        // Store in VFS
62                        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                        // Write to file system
75                        tokio::fs::write(storage_path, &data).await?;
76                    }
77                    crate::fixtures::UploadStorage::Discard => {
78                        // Do nothing
79                    }
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}