fusibile 0.2.0

A CLI to mount remote file systems locally via FUSE or Dokany
use std::path::PathBuf;

use remotefs::fs::UnixPex;
use remotefs::{RemoteFs, RemoteResult};
#[cfg(all(feature = "smb", target_family = "unix"))]
use remotefs_smb::PavaoSmbFs as SmbFs;
#[cfg(all(feature = "smb", target_family = "windows"))]
use remotefs_smb::WNetSmbFs as SmbFs;
#[cfg(feature = "ssh")]
use remotefs_ssh::{NoCheckServerKey, RusshSession};

/// Wrapper around the different [`RemoteFs`] implementations
// `#[expect]` isn't usable here: whether this lint fires depends on which backend features are
// enabled (with a single feature, or none, there's no size disparity to flag), so an `#[expect]`
// would spuriously fail the build on those feature combinations.
#[allow(
    clippy::large_enum_variant,
    reason = "backends have very different sizes (e.g. SSH session buffers vs the trivial in-memory backend); this enum is only ever constructed once at startup, so boxing the large variants would only add indirection for no real benefit"
)]
pub enum RemoteFsWrapper {
    /// AWS S3 backend.
    #[cfg(feature = "aws-s3")]
    Aws(remotefs_aws_s3::AwsS3Fs),
    /// FTP/FTPS backend.
    #[cfg(feature = "ftp")]
    Ftp(remotefs_ftp::FtpFs),
    /// Google Cloud Storage backend.
    #[cfg(feature = "gcs")]
    Gcs(remotefs_gcs::GoogleCloudStorageFs),
    /// Kubernetes pod backend.
    #[cfg(feature = "kube")]
    Kube(remotefs_kube::KubeMultiPodFs),
    /// In-memory backend, mostly useful for testing.
    Memory(remotefs_memory::MemoryFs),
    /// SCP backend.
    #[cfg(feature = "ssh")]
    Scp(remotefs_ssh::ScpFs<RusshSession<NoCheckServerKey>>),
    /// SFTP backend.
    #[cfg(feature = "ssh")]
    Sftp(remotefs_ssh::SftpFs<RusshSession<NoCheckServerKey>>),
    /// SMB backend.
    #[cfg(feature = "smb")]
    Smb(SmbFs),
    /// WebDAV backend.
    #[cfg(feature = "webdav")]
    Webdav(remotefs_webdav::WebDAVFs),
}

impl RemoteFsWrapper {
    /// Call the given closure with the appropriate [`RemoteFs`] implementation
    fn on_remote<F, T>(&mut self, f: F) -> T
    where
        F: FnOnce(&mut dyn RemoteFs) -> T,
    {
        match self {
            #[cfg(feature = "aws-s3")]
            RemoteFsWrapper::Aws(fs) => f(fs),
            #[cfg(feature = "ftp")]
            RemoteFsWrapper::Ftp(fs) => f(fs),
            #[cfg(feature = "gcs")]
            RemoteFsWrapper::Gcs(fs) => f(fs),
            #[cfg(feature = "kube")]
            RemoteFsWrapper::Kube(fs) => f(fs),
            RemoteFsWrapper::Memory(fs) => f(fs),
            #[cfg(feature = "ssh")]
            RemoteFsWrapper::Scp(fs) => f(fs),
            #[cfg(feature = "ssh")]
            RemoteFsWrapper::Sftp(fs) => f(fs),
            #[cfg(feature = "smb")]
            RemoteFsWrapper::Smb(fs) => f(fs),
            #[cfg(feature = "webdav")]
            RemoteFsWrapper::Webdav(fs) => f(fs),
        }
    }
}

impl RemoteFs for RemoteFsWrapper {
    fn append(
        &mut self,
        path: &std::path::Path,
        metadata: &remotefs::fs::Metadata,
    ) -> RemoteResult<remotefs::fs::WriteStream> {
        self.on_remote(|fs| fs.append(path, metadata))
    }

    fn append_file(
        &mut self,
        path: &std::path::Path,
        metadata: &remotefs::fs::Metadata,
        reader: Box<dyn std::io::Read + Send>,
    ) -> RemoteResult<u64> {
        self.on_remote(|fs| fs.append_file(path, metadata, reader))
    }

    fn create_dir(&mut self, path: &std::path::Path, mode: UnixPex) -> RemoteResult<()> {
        self.on_remote(|fs| fs.create_dir(path, mode))
    }

    fn change_dir(&mut self, dir: &std::path::Path) -> RemoteResult<PathBuf> {
        self.on_remote(|fs| fs.change_dir(dir))
    }

    fn connect(&mut self) -> RemoteResult<remotefs::fs::Welcome> {
        self.on_remote(|fs| fs.connect())
    }

    fn copy(&mut self, src: &std::path::Path, dest: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.copy(src, dest))
    }

    fn create(
        &mut self,
        path: &std::path::Path,
        metadata: &remotefs::fs::Metadata,
    ) -> RemoteResult<remotefs::fs::WriteStream> {
        self.on_remote(|fs| fs.create(path, metadata))
    }

    fn create_file(
        &mut self,
        path: &std::path::Path,
        metadata: &remotefs::fs::Metadata,
        reader: Box<dyn std::io::Read + Send>,
    ) -> RemoteResult<u64> {
        self.on_remote(|fs| fs.create_file(path, metadata, reader))
    }

    fn disconnect(&mut self) -> RemoteResult<()> {
        self.on_remote(|fs| fs.disconnect())
    }

    fn exec(&mut self, cmd: &str) -> RemoteResult<(u32, String)> {
        self.on_remote(|fs| fs.exec(cmd))
    }

    fn exists(&mut self, path: &std::path::Path) -> RemoteResult<bool> {
        self.on_remote(|fs| fs.exists(path))
    }

    fn find(&mut self, search: &str) -> RemoteResult<Vec<remotefs::File>> {
        self.on_remote(|fs| fs.find(search))
    }

    fn is_connected(&mut self) -> bool {
        self.on_remote(|fs| fs.is_connected())
    }

    fn list_dir(&mut self, path: &std::path::Path) -> RemoteResult<Vec<remotefs::File>> {
        self.on_remote(|fs| fs.list_dir(path))
    }

    fn mov(&mut self, src: &std::path::Path, dest: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.mov(src, dest))
    }

    fn on_read(&mut self, readable: remotefs::fs::ReadStream) -> RemoteResult<()> {
        self.on_remote(|fs| fs.on_read(readable))
    }

    fn on_written(&mut self, writable: remotefs::fs::WriteStream) -> RemoteResult<()> {
        self.on_remote(|fs| fs.on_written(writable))
    }

    fn open(&mut self, path: &std::path::Path) -> RemoteResult<remotefs::fs::ReadStream> {
        self.on_remote(|fs| fs.open(path))
    }

    fn open_file(
        &mut self,
        src: &std::path::Path,
        dest: Box<dyn std::io::Write + Send>,
    ) -> RemoteResult<u64> {
        self.on_remote(|fs| fs.open_file(src, dest))
    }

    fn pwd(&mut self) -> RemoteResult<PathBuf> {
        self.on_remote(|fs| fs.pwd())
    }

    fn remove_dir(&mut self, path: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.remove_dir(path))
    }

    fn remove_dir_all(&mut self, path: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.remove_dir_all(path))
    }

    fn remove_file(&mut self, path: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.remove_file(path))
    }

    fn setstat(
        &mut self,
        path: &std::path::Path,
        metadata: remotefs::fs::Metadata,
    ) -> RemoteResult<()> {
        self.on_remote(|fs| fs.setstat(path, metadata))
    }

    fn stat(&mut self, path: &std::path::Path) -> RemoteResult<remotefs::File> {
        self.on_remote(|fs| fs.stat(path))
    }

    fn symlink(&mut self, path: &std::path::Path, target: &std::path::Path) -> RemoteResult<()> {
        self.on_remote(|fs| fs.symlink(path, target))
    }
}