libnoa 0.3.2

AI-native distributed version control
Documentation
#[cfg(feature = "ftp")]
pub mod ftp_impl;
pub mod ipfs_impl;
pub mod minio_impl;
mod redb_impl;
pub mod sftp_impl;

use async_trait::async_trait;
use serde::{Deserialize, Serialize};

#[cfg(feature = "ftp")]
pub use ftp_impl::FtpObjectStore;
pub use ipfs_impl::IpfsObjectStore;
pub use minio_impl::MinioObjectStore;
pub use redb_impl::RedbObjectStore;
pub use sftp_impl::SftpObjectStore;
use sha2::{Digest, Sha256};

use crate::error::Result;

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BlobId(pub String);

impl BlobId {
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }
}

impl std::fmt::Display for BlobId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TreeId(pub String);

impl TreeId {
    #[must_use]
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }
}

impl std::fmt::Display for TreeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TreeEntry {
    pub name: String,
    pub kind: EntryKind,
    pub id: String,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EntryKind {
    Blob,
    Tree,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TreeEntries(pub Vec<TreeEntry>);

impl Default for TreeEntries {
    fn default() -> Self {
        Self::new()
    }
}

impl TreeEntries {
    #[must_use]
    pub fn new() -> Self {
        TreeEntries(Vec::new())
    }

    pub fn sort(&mut self) {
        self.0.sort_by(|a, b| a.name.cmp(&b.name));
    }
}

#[must_use]
pub fn sha256_hex(data: &[u8]) -> String {
    let mut hasher = Sha256::new();
    hasher.update(data);
    hex::encode(hasher.finalize())
}

#[async_trait]
pub trait ObjectStore: Send + Sync {
    async fn put_blob(&self, content: &[u8]) -> Result<BlobId>;
    async fn get_blob(&self, id: &BlobId) -> Result<Vec<u8>>;
    async fn has_blob(&self, id: &BlobId) -> Result<bool>;
    async fn put_tree(&self, entries: &TreeEntries) -> Result<TreeId>;
    async fn get_tree(&self, id: &TreeId) -> Result<TreeEntries>;
    async fn has_tree(&self, id: &TreeId) -> Result<bool>;
}

pub async fn create_remote_store(
    config: &crate::config::StorageConfig,
) -> Result<Box<dyn ObjectStore>> {
    match config.backend_type {
        crate::config::StorageProtocol::Ipfs => {
            let ipfs_env = std::env::var("IPFS_API").ok();
            let endpoint = config
                .endpoint
                .as_deref()
                .or(ipfs_env.as_deref())
                .unwrap_or("http://127.0.0.1:5001");
            Ok(Box::new(IpfsObjectStore::new(
                endpoint,
                config.auth_token.clone(),
            )))
        }
        crate::config::StorageProtocol::S3 | crate::config::StorageProtocol::Minio => {
            let store = MinioObjectStore::from_transport_config(config).await?;
            Ok(Box::new(store))
        }
        #[cfg(feature = "ftp")]
        crate::config::StorageProtocol::Ftp | crate::config::StorageProtocol::Ftps => {
            let store = FtpObjectStore::from_config(config)?;
            Ok(Box::new(store))
        }
        crate::config::StorageProtocol::Sftp => {
            let store = SftpObjectStore::from_config(config)?;
            Ok(Box::new(store))
        }
    }
}