use std::ops::{Deref, DerefMut};
use std::path::Path;
use async_trait::async_trait;
use crate::error::{Error, Result};
use crate::traits::RemoteFileSystem;
use crate::types::{Config, FileEntry, FileMeta, Protocol};
#[cfg(feature = "ftp")]
use crate::ftp::FtpClient;
#[cfg(feature = "ftp")]
use crate::types::FtpConfig;
#[cfg(feature = "sftp")]
use crate::sftp::SftpClient;
#[cfg(feature = "sftp")]
use crate::types::SftpConfig;
#[cfg(feature = "smb")]
use crate::smb::SmbClient;
#[cfg(feature = "smb")]
use crate::types::SmbConfig;
pub struct Client {
protocol: Protocol,
inner: Box<dyn RemoteFileSystem>,
}
impl Client {
#[cfg_attr(
not(any(feature = "smb", feature = "ftp", feature = "sftp")),
allow(dead_code)
)]
fn new(protocol: Protocol, inner: Box<dyn RemoteFileSystem>) -> Self {
Self { protocol, inner }
}
pub async fn connect_with(config: &Config) -> Result<Self> {
#[cfg(not(any(feature = "smb", feature = "ftp", feature = "sftp")))]
{
let _ = config;
return Err(Error::FeatureDisabled(
"enable at least one of: smb, ftp, sftp",
));
}
#[cfg(any(feature = "smb", feature = "ftp", feature = "sftp"))]
{
let (protocol, mut inner): (Protocol, Box<dyn RemoteFileSystem>) = match config {
#[cfg(feature = "smb")]
Config::Smb(cfg) => (Protocol::Smb, Box::new(SmbClient::new(cfg.clone()))),
#[cfg(feature = "ftp")]
Config::Ftp(cfg) => (Protocol::Ftp, Box::new(FtpClient::new(cfg.clone()))),
#[cfg(feature = "sftp")]
Config::Sftp(cfg) => (Protocol::Sftp, Box::new(SftpClient::new(cfg.clone()))),
};
inner.connect().await?;
Ok(Self::new(protocol, inner))
}
}
#[cfg(feature = "smb")]
pub async fn smb(config: &SmbConfig) -> Result<Self> {
Self::connect_with(&Config::Smb(config.clone())).await
}
#[cfg(feature = "ftp")]
pub async fn ftp(config: &FtpConfig) -> Result<Self> {
Self::connect_with(&Config::Ftp(config.clone())).await
}
#[cfg(feature = "sftp")]
pub async fn sftp(config: &SftpConfig) -> Result<Self> {
Self::connect_with(&Config::Sftp(config.clone())).await
}
pub fn protocol(&self) -> Protocol {
self.protocol
}
pub fn as_fs(&self) -> &dyn RemoteFileSystem {
&*self.inner
}
pub fn as_fs_mut(&mut self) -> &mut dyn RemoteFileSystem {
&mut *self.inner
}
}
impl Deref for Client {
type Target = dyn RemoteFileSystem;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl DerefMut for Client {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.inner
}
}
#[async_trait]
impl RemoteFileSystem for Client {
async fn connect(&mut self) -> Result<()> {
self.inner.connect().await
}
async fn disconnect(&mut self) -> Result<()> {
self.inner.disconnect().await
}
fn is_connected(&self) -> bool {
self.inner.is_connected()
}
async fn list(&mut self, path: &str) -> Result<Vec<FileEntry>> {
self.inner.list(path).await
}
async fn metadata(&mut self, path: &str) -> Result<FileMeta> {
self.inner.metadata(path).await
}
async fn exists(&mut self, path: &str) -> Result<bool> {
self.inner.exists(path).await
}
async fn mkdir(&mut self, path: &str) -> Result<()> {
self.inner.mkdir(path).await
}
async fn mkdir_all(&mut self, path: &str) -> Result<()> {
self.inner.mkdir_all(path).await
}
async fn rmdir(&mut self, path: &str) -> Result<()> {
self.inner.rmdir(path).await
}
async fn remove_file(&mut self, path: &str) -> Result<()> {
self.inner.remove_file(path).await
}
async fn remove(&mut self, path: &str) -> Result<()> {
self.inner.remove(path).await
}
async fn rename(&mut self, from: &str, to: &str) -> Result<()> {
self.inner.rename(from, to).await
}
async fn read(&mut self, path: &str) -> Result<Vec<u8>> {
self.inner.read(path).await
}
async fn write(&mut self, path: &str, data: &[u8]) -> Result<()> {
self.inner.write(path, data).await
}
async fn download(&mut self, remote: &str, local: &Path) -> Result<()> {
self.inner.download(remote, local).await
}
async fn upload(&mut self, local: &Path, remote: &str) -> Result<()> {
self.inner.upload(local, remote).await
}
}
#[derive(Debug, Default)]
pub struct ClientBuilder {
config: Option<Config>,
}
impl ClientBuilder {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "smb")]
pub fn smb(mut self, config: SmbConfig) -> Self {
self.config = Some(Config::Smb(config));
self
}
#[cfg(feature = "ftp")]
pub fn ftp(mut self, config: FtpConfig) -> Self {
self.config = Some(Config::Ftp(config));
self
}
#[cfg(feature = "sftp")]
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
}
}