libalternatives-rs 0.1.0

Rust bindings for libalternatives library from OpenSUSE
pub mod ffi;

use ffi::*;
use num_enum::TryFromPrimitive;

#[derive(TryFromPrimitive, Debug, Clone)]
#[repr(i32)]
pub enum LinkType {
    Binary = 0,
    ManPage = 1,
    Group = 2,
}

#[derive(TryFromPrimitive, Debug, Clone)]
#[repr(i32)]
pub enum LinkOptions {
    None = 0,
    KeepARGV0 = 1,
    UpdateARGV0 = 2,
}

#[derive(Clone, Debug)]
pub struct Alternative {
    pub priority: i32,
    pub r#type: LinkType,
    pub target: String,
    pub options: LinkOptions,
}

pub enum Config {
    /// Use system config path
    System,
    /// Use user config path
    User,
    /// Use custom config path
    Custom(String),
}

impl Alternative {
    /// Create new alternative object
    pub fn new<T>(priority: i32, r#type: LinkType, target: T, options: LinkOptions) -> Alternative
    where
        T: ToString,
    {
        Alternative {
            priority,
            r#type,
            target: target.to_string(),
            options,
        }
    }

    /// Returns binary `bin` alternative with highest priority
    pub fn with_highest_priority<T>(bin: T) -> Result<Alternative, Box<dyn std::error::Error>>
    where
        T: ToString,
    {
        let name = std::ffi::CString::new(bin.to_string())?;

        unsafe {
            let mut alternative_ref: *mut AlternativeLink = std::ptr::null_mut();

            if libalts_load_highest_priority_binary_alternatives(
                name.as_ptr(),
                &mut alternative_ref,
            ) != 0
            {
                return Err(Box::new(std::io::Error::last_os_error()));
            }

            let alternative = alternative_ref.as_ref().unwrap().to_alternative();

            libalts_free_alternatives_ptr(&mut alternative_ref);

            Ok(alternative)
        }
    }

    /// Returns binary `bin` alternative with priority `prio`
    pub fn with_priority<T>(bin: T, prio: i32) -> Result<Alternative, Box<dyn std::error::Error>>
    where
        T: ToString,
    {
        let name = std::ffi::CString::new(bin.to_string())?;

        unsafe {
            let mut alternative_ref: *mut AlternativeLink = std::ptr::null_mut();

            if libalts_load_exact_priority_binary_alternatives(
                name.as_ptr(),
                prio,
                &mut alternative_ref,
            ) != 0
            {
                return Err(Box::new(std::io::Error::last_os_error()));
            }

            let alternative = alternative_ref.as_ref().unwrap().to_alternative();

            libalts_free_alternatives_ptr(&mut alternative_ref);

            Ok(alternative)
        }
    }

    /// Returns all available binaries from config directory
    pub fn get_binaries() -> Result<Vec<String>, Box<dyn std::error::Error>> {
        unsafe {
            let mut size: libc::size_t = 0;
            let mut binaries_ref = std::ptr::null_mut();

            if libalts_load_available_binaries(&mut binaries_ref, &mut size) != 0 {
                return Err(Box::new(std::io::Error::last_os_error()));
            }

            let binaries = Ok(std::slice::from_raw_parts(binaries_ref, size)
                .as_ref()
                .into_iter()
                .map(|s| std::ffi::CStr::from_ptr(*s).to_str().unwrap().to_string())
                .collect());

            for i in 0..size {
                libc::free(*binaries_ref.add(i) as *mut libc::c_void);
            }

            libc::free(binaries_ref as *mut libc::c_void);

            binaries
        }
    }

    /// Returns binary alternatives
    pub fn get_binary_alternatives<T>(
        bin: T,
    ) -> Result<Vec<Alternative>, Box<dyn std::error::Error>>
    where
        T: ToString + Clone,
    {
        let name = std::ffi::CString::new(bin.to_string())?;

        unsafe {
            let mut size: libc::size_t = 0;
            let mut prios = std::ptr::null_mut();

            if libalts_load_binary_priorities(name.as_ptr(), &mut prios, &mut size) != 0 {
                return Err(Box::new(std::io::Error::last_os_error()));
            }

            let mut alts: Vec<Alternative> = Vec::with_capacity(size);

            let prios_vec = std::slice::from_raw_parts(prios, size)
                .as_ref()
                .iter()
                .collect::<Vec<&i32>>();

            for prio in prios_vec {
                alts.push(Self::with_priority(bin.clone(), *prio)?);
            }

            libc::free(prios as *mut libc::c_void);

            Ok(alts)
        }
    }

    /// Read binary priority from config file
    pub fn read_priority_from_config<T>(
        bin: T,
        config_file: Config,
    ) -> Result<i32, Box<dyn std::error::Error>>
    where
        T: ToString,
    {
        let bin = std::ffi::CString::new(bin.to_string())?;
        let config_file = std::ffi::CString::new(Self::get_config_path(config_file))?;

        let res: i32;

        unsafe {
            res = libalts_read_binary_configured_priority_from_file(
                bin.as_ptr(),
                config_file.as_ptr(),
            );
        }

        if res < 0 {
            return Err(Box::new(std::io::Error::last_os_error()));
        }

        Ok(res)
    }

    /// Write binary priority to config file
    pub fn write_priority_to_file<T>(
        bin: T,
        prio: u32,
        config_file: Config,
    ) -> Result<(), Box<dyn std::error::Error>>
    where
        T: ToString,
    {
        let bin = std::ffi::CString::new(bin.to_string())?;
        let config_file = std::ffi::CString::new(Self::get_config_path(config_file))?;

        let res: i32 = unsafe {
            libalts_write_binary_configured_priority_to_file(
                bin.as_ptr(),
                prio as i32,
                config_file.as_ptr(),
            )
        };

        if res != 0 {
            return Err(Box::new(std::io::Error::last_os_error()));
        }

        Ok(())
    }

    pub fn get_system_config_path() -> String {
        unsafe {
            let str_ptr = libalts_get_system_config_path();

            std::ffi::CStr::from_ptr(str_ptr)
                .to_string_lossy()
                .to_string()
        }
    }

    pub fn get_user_config_path() -> String {
        unsafe {
            let str_ptr = libalts_get_system_config_path();

            std::ffi::CStr::from_ptr(str_ptr)
                .to_string_lossy()
                .to_string()
        }
    }

    pub fn get_config_path(config: Config) -> String {
        match config {
            Config::System => Self::get_system_config_path(),
            Config::User => Self::get_user_config_path(),
            Config::Custom(x) => x,
        }
    }

    pub fn check_if_config_exists(config: Config) -> Result<bool, Box<dyn std::error::Error>> {
        Ok(std::fs::exists(Self::get_config_path(config))?)
    }
}

impl std::fmt::Display for Alternative {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Target: {}, Priority: {}, Options: {:#?}, Type: {:#?}",
            self.target, self.priority, self.options, self.r#type,
        )
    }
}