remote-fs 0.1.0

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

use async_trait::async_trait;

use crate::error::{Error, Result};
use crate::ftp::FtpClient;
use crate::sftp::SftpClient;
use crate::smb::SmbClient;
use crate::traits::RemoteFileSystem;
use crate::types::{Config, FileEntry, FileMeta, FtpConfig, Protocol, SftpConfig, SmbConfig};

/// Unified client that dispatches to SMB / FTP / SFTP backends.
pub enum Client {
    Smb(SmbClient),
    Ftp(FtpClient),
    Sftp(SftpClient),
}

impl Client {
    /// Create a client from a generic config and connect.
    pub async fn connect_with(config: &Config) -> Result<Self> {
        let mut client = match config {
            Config::Smb(cfg) => Client::Smb(SmbClient::new(cfg.clone())),
            Config::Ftp(cfg) => Client::Ftp(FtpClient::new(cfg.clone())),
            Config::Sftp(cfg) => Client::Sftp(SftpClient::new(cfg.clone())),
        };
        client.connect().await?;
        Ok(client)
    }

    /// Create an SMB client and connect.
    pub async fn smb(config: &SmbConfig) -> Result<Self> {
        Self::connect_with(&Config::Smb(config.clone())).await
    }

    /// Create an FTP client and connect.
    pub async fn ftp(config: &FtpConfig) -> Result<Self> {
        Self::connect_with(&Config::Ftp(config.clone())).await
    }

    /// Create an SFTP client and connect.
    pub async fn sftp(config: &SftpConfig) -> Result<Self> {
        Self::connect_with(&Config::Sftp(config.clone())).await
    }

    /// Active protocol of this client.
    pub fn protocol(&self) -> Protocol {
        match self {
            Client::Smb(_) => Protocol::Smb,
            Client::Ftp(_) => Protocol::Ftp,
            Client::Sftp(_) => Protocol::Sftp,
        }
    }
}

#[async_trait]
impl RemoteFileSystem for Client {
    async fn connect(&mut self) -> Result<()> {
        match self {
            Client::Smb(c) => c.connect().await,
            Client::Ftp(c) => c.connect().await,
            Client::Sftp(c) => c.connect().await,
        }
    }

    async fn disconnect(&mut self) -> Result<()> {
        match self {
            Client::Smb(c) => c.disconnect().await,
            Client::Ftp(c) => c.disconnect().await,
            Client::Sftp(c) => c.disconnect().await,
        }
    }

    fn is_connected(&self) -> bool {
        match self {
            Client::Smb(c) => c.is_connected(),
            Client::Ftp(c) => c.is_connected(),
            Client::Sftp(c) => c.is_connected(),
        }
    }

    async fn list(&mut self, path: &str) -> Result<Vec<FileEntry>> {
        match self {
            Client::Smb(c) => c.list(path).await,
            Client::Ftp(c) => c.list(path).await,
            Client::Sftp(c) => c.list(path).await,
        }
    }

    async fn metadata(&mut self, path: &str) -> Result<FileMeta> {
        match self {
            Client::Smb(c) => c.metadata(path).await,
            Client::Ftp(c) => c.metadata(path).await,
            Client::Sftp(c) => c.metadata(path).await,
        }
    }

    async fn exists(&mut self, path: &str) -> Result<bool> {
        match self {
            Client::Smb(c) => c.exists(path).await,
            Client::Ftp(c) => c.exists(path).await,
            Client::Sftp(c) => c.exists(path).await,
        }
    }

    async fn mkdir(&mut self, path: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.mkdir(path).await,
            Client::Ftp(c) => c.mkdir(path).await,
            Client::Sftp(c) => c.mkdir(path).await,
        }
    }

    async fn mkdir_all(&mut self, path: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.mkdir_all(path).await,
            Client::Ftp(c) => c.mkdir_all(path).await,
            Client::Sftp(c) => c.mkdir_all(path).await,
        }
    }

    async fn rmdir(&mut self, path: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.rmdir(path).await,
            Client::Ftp(c) => c.rmdir(path).await,
            Client::Sftp(c) => c.rmdir(path).await,
        }
    }

    async fn remove_file(&mut self, path: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.remove_file(path).await,
            Client::Ftp(c) => c.remove_file(path).await,
            Client::Sftp(c) => c.remove_file(path).await,
        }
    }

    async fn remove(&mut self, path: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.remove(path).await,
            Client::Ftp(c) => c.remove(path).await,
            Client::Sftp(c) => c.remove(path).await,
        }
    }

    async fn rename(&mut self, from: &str, to: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.rename(from, to).await,
            Client::Ftp(c) => c.rename(from, to).await,
            Client::Sftp(c) => c.rename(from, to).await,
        }
    }

    async fn read(&mut self, path: &str) -> Result<Vec<u8>> {
        match self {
            Client::Smb(c) => c.read(path).await,
            Client::Ftp(c) => c.read(path).await,
            Client::Sftp(c) => c.read(path).await,
        }
    }

    async fn write(&mut self, path: &str, data: &[u8]) -> Result<()> {
        match self {
            Client::Smb(c) => c.write(path, data).await,
            Client::Ftp(c) => c.write(path, data).await,
            Client::Sftp(c) => c.write(path, data).await,
        }
    }

    async fn download(&mut self, remote: &str, local: &Path) -> Result<()> {
        match self {
            Client::Smb(c) => c.download(remote, local).await,
            Client::Ftp(c) => c.download(remote, local).await,
            Client::Sftp(c) => c.download(remote, local).await,
        }
    }

    async fn upload(&mut self, local: &Path, remote: &str) -> Result<()> {
        match self {
            Client::Smb(c) => c.upload(local, remote).await,
            Client::Ftp(c) => c.upload(local, remote).await,
            Client::Sftp(c) => c.upload(local, remote).await,
        }
    }
}

/// Builder for constructing a [`Client`].
#[derive(Debug, Default)]
pub struct ClientBuilder {
    config: Option<Config>,
}

impl ClientBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn smb(mut self, config: SmbConfig) -> Self {
        self.config = Some(Config::Smb(config));
        self
    }

    pub fn ftp(mut self, config: FtpConfig) -> Self {
        self.config = Some(Config::Ftp(config));
        self
    }

    pub fn sftp(mut self, config: SftpConfig) -> Self {
        self.config = Some(Config::Sftp(config));
        self
    }

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

    pub async fn build(self) -> Result<Client> {
        let config = self
            .config
            .ok_or_else(|| Error::Other("missing connection config".into()))?;
        Client::connect_with(&config).await
    }
}