remote-fs 0.1.2

Unified async file operations over SMB, FTP, and SFTP
Documentation
use std::path::PathBuf;
use std::time::SystemTime;

/// Supported remote protocols.
///
/// Variants always exist for stable API; backends are gated by Cargo features.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
    Smb,
    Ftp,
    Sftp,
}

/// File or directory entry returned by listing.
#[derive(Debug, Clone)]
pub struct FileEntry {
    pub path: PathBuf,
    pub name: String,
    pub is_dir: bool,
    pub is_file: bool,
    pub is_symlink: bool,
    pub size: u64,
    pub modified: Option<SystemTime>,
}

/// Metadata for a remote path.
#[derive(Debug, Clone)]
pub struct FileMeta {
    pub path: PathBuf,
    pub is_dir: bool,
    pub is_file: bool,
    pub is_symlink: bool,
    pub size: u64,
    pub modified: Option<SystemTime>,
}

/// SMB connection configuration.
#[cfg(feature = "smb")]
#[derive(Debug, Clone)]
pub struct SmbConfig {
    pub host: String,
    pub share: String,
    pub username: String,
    pub password: String,
    pub port: Option<u16>,
    pub workgroup: Option<String>,
}

#[cfg(feature = "smb")]
impl SmbConfig {
    pub fn new(
        host: impl Into<String>,
        share: impl Into<String>,
        username: impl Into<String>,
        password: impl Into<String>,
    ) -> Self {
        Self {
            host: host.into(),
            share: share.into(),
            username: username.into(),
            password: password.into(),
            port: None,
            workgroup: None,
        }
    }

    pub fn port(mut self, port: u16) -> Self {
        self.port = Some(port);
        self
    }

    pub fn workgroup(mut self, workgroup: impl Into<String>) -> Self {
        self.workgroup = Some(workgroup.into());
        self
    }
}

/// FTP connection configuration.
#[cfg(feature = "ftp")]
#[derive(Debug, Clone)]
pub struct FtpConfig {
    pub host: String,
    pub port: u16,
    pub username: String,
    pub password: String,
    pub passive: bool,
}

#[cfg(feature = "ftp")]
impl FtpConfig {
    pub fn new(
        host: impl Into<String>,
        username: impl Into<String>,
        password: impl Into<String>,
    ) -> Self {
        Self {
            host: host.into(),
            port: 21,
            username: username.into(),
            password: password.into(),
            passive: true,
        }
    }

    pub fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    pub fn passive(mut self, passive: bool) -> Self {
        self.passive = passive;
        self
    }
}

/// SFTP connection configuration.
#[cfg(feature = "sftp")]
#[derive(Debug, Clone)]
pub struct SftpConfig {
    pub host: String,
    pub port: u16,
    pub username: String,
    pub password: Option<String>,
    pub private_key_path: Option<PathBuf>,
}

#[cfg(feature = "sftp")]
impl SftpConfig {
    pub fn with_password(
        host: impl Into<String>,
        username: impl Into<String>,
        password: impl Into<String>,
    ) -> Self {
        Self {
            host: host.into(),
            port: 22,
            username: username.into(),
            password: Some(password.into()),
            private_key_path: None,
        }
    }

    pub fn with_key(
        host: impl Into<String>,
        username: impl Into<String>,
        private_key_path: impl Into<PathBuf>,
    ) -> Self {
        Self {
            host: host.into(),
            port: 22,
            username: username.into(),
            password: None,
            private_key_path: Some(private_key_path.into()),
        }
    }

    pub fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }
}

/// Generic connection configuration.
#[derive(Debug, Clone)]
pub enum Config {
    #[cfg(feature = "smb")]
    Smb(SmbConfig),
    #[cfg(feature = "ftp")]
    Ftp(FtpConfig),
    #[cfg(feature = "sftp")]
    Sftp(SftpConfig),
}

impl Config {
    pub fn protocol(&self) -> Protocol {
        match self {
            #[cfg(feature = "smb")]
            Config::Smb(_) => Protocol::Smb,
            #[cfg(feature = "ftp")]
            Config::Ftp(_) => Protocol::Ftp,
            #[cfg(feature = "sftp")]
            Config::Sftp(_) => Protocol::Sftp,
            // When no protocol features are enabled, `Config` has no variants.
            #[cfg(not(any(feature = "smb", feature = "ftp", feature = "sftp")))]
            _ => unreachable!("enable at least one of: smb, ftp, sftp"),
        }
    }
}