liboxen 0.53.0

Oxen is a fast data version control system, built with machine learning training data in mind. Designed to handle terabytes of data with ease, using a workflow similar to git. Version both structured and unstructured data of any modality: text, images, video, audio, CSV, Parquet, JSONL, model checkpoints, and more. liboxen is the embeddable core library behind the oxen CLI and server, which power fine tuning and inference pipelines for multimodal LLMs, image models, and video models on Oxen.ai.
use crate::error::OxenError;
use crate::model::User;
use crate::util;
use crate::util::fs::AtomicFile;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

pub const USER_CONFIG_FILENAME: &str = "user_config.toml";

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserConfig {
    pub name: String,
    pub email: String,
    #[serde(default)]
    pub editor: Option<String>,
}

impl UserConfig {
    pub fn new(path: &Path) -> UserConfig {
        let contents = util::fs::read_from_path(path).unwrap();
        toml::from_str(&contents).unwrap()
    }

    pub fn from_user(user: &User) -> UserConfig {
        UserConfig {
            name: user.name.to_owned(),
            email: user.email.to_owned(),
            editor: None,
        }
    }

    pub fn to_user(&self) -> User {
        User {
            name: self.name.to_owned(),
            email: self.email.to_owned(),
        }
    }

    fn new_empty() -> UserConfig {
        UserConfig {
            name: String::from(""),
            email: String::from(""),
            editor: None,
        }
    }

    pub fn get() -> Result<UserConfig, OxenError> {
        let config_file = util::fs::oxen_config_dir()?.join(Path::new(USER_CONFIG_FILENAME));

        log::debug!("looking for config file in...{config_file:?}");
        if config_file.exists() {
            Ok(UserConfig::new(&config_file))
        } else {
            log::debug!(
                "unable to find config file at {:?}. Current working directory is {:?}",
                config_file,
                std::env::current_dir().unwrap()
            );
            Err(OxenError::email_and_name_not_set())
        }
    }

    pub fn identifier() -> Result<String, OxenError> {
        Ok(util::hasher::hash_str_sha256(
            UserConfig::get()?.to_user().email,
        ))
    }

    pub fn get_or_create() -> Result<UserConfig, OxenError> {
        match Self::get() {
            Ok(config) => Ok(config),
            Err(_err) => {
                let config = Self::new_empty();
                config.save_default()?;
                let config_file = util::fs::oxen_config_dir()?.join(USER_CONFIG_FILENAME);
                println!("🐂 created a new config file at {}", config_file.display());
                Ok(config)
            }
        }
    }

    pub fn save_default(&self) -> Result<(), OxenError> {
        let config_dir = util::fs::oxen_config_dir()?;
        let config_file = config_dir.join(Path::new(USER_CONFIG_FILENAME));
        log::debug!("Saving config to {config_file:?}");
        if !config_dir.exists() {
            fs::create_dir_all(config_dir)?;
        }
        self.save(&config_file)?;
        Ok(())
    }

    pub fn save(&self, path: &Path) -> Result<(), OxenError> {
        let toml = toml::to_string(&self)?;
        AtomicFile::new(path).write(toml.as_bytes())?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::config::UserConfig;
    use crate::error::OxenError;
    use crate::test;

    use std::path::Path;

    #[test]
    fn test_read() {
        let config = UserConfig::new(&test::user_cfg_file());
        assert!(!config.name.is_empty());
        assert!(!config.email.is_empty());
    }

    #[test]
    fn test_save() -> Result<(), OxenError> {
        let final_path = Path::new("test_save_config.toml");
        let orig_config = UserConfig::new(&test::user_cfg_file());

        orig_config.save(final_path)?;

        let config = UserConfig::new(final_path);
        assert!(!config.name.is_empty());

        std::fs::remove_file(final_path)?;
        Ok(())
    }
}