owfs 0.1.0

Rust API for OWFS (1-Wire file system)
Documentation
#![ doc = include_str!( concat!( env!( "CARGO_MANIFEST_DIR" ), "/", "README.md" ) ) ]
use std::collections::HashSet;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::c_char;
use std::ptr;

mod devices;
mod owcapi;

pub use devices::{Device, DeviceInfo};

#[derive(Debug)]
pub struct Error {
    code: isize,
}

impl From<std::ffi::NulError> for Error {
    fn from(_err: std::ffi::NulError) -> Self {
        Self::new(0)
    }
}

impl From<std::num::TryFromIntError> for Error {
    fn from(_err: std::num::TryFromIntError) -> Self {
        Self::new(1)
    }
}

impl Error {
    #[inline]
    pub fn new(code: isize) -> Self {
        Self { code }
    }
    #[inline]
    pub fn code(&self) -> isize {
        self.code
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "OWFS error: {}", self.code)
    }
}

impl std::error::Error for Error {}

pub struct OwfsGuard {}

impl Drop for OwfsGuard {
    #[inline]
    fn drop(&mut self) {
        finish();
    }
}

pub fn init(path: &str) -> Result<OwfsGuard, Error> {
    let c_path = CString::new(path)?;
    let res = unsafe { owcapi::OW_init(c_path.as_ptr()) };
    if res == 0 {
        Ok(OwfsGuard {})
    } else {
        Err(Error::new(res))
    }
}

pub fn get(path: &str) -> Result<String, Error> {
    let c_path = CString::new(path)?;
    let mut buf: *mut c_char = ptr::null_mut();
    let buf_ptr: *const *mut c_char = &mut buf;
    let mut buf_length: usize = 0;
    let res = unsafe { owcapi::OW_get(c_path.as_ptr(), buf_ptr, &mut buf_length) };
    if res >= 0 {
        let data = unsafe { CStr::from_ptr(buf) };
        let result = data.to_string_lossy().to_string();
        unsafe {
            libc::free(buf.cast::<libc::c_void>());
        }
        Ok(result)
    } else {
        unsafe {
            libc::free(buf.cast::<libc::c_void>());
        }
        Err(Error::new(res))
    }
}

pub fn get_bytes(path: &str) -> Result<Vec<u8>, Error> {
    let c_path = CString::new(path)?;
    let mut buf: *mut c_char = ptr::null_mut();
    let buf_ptr: *const *mut c_char = &mut buf;
    let mut buf_length: usize = 0;
    let res = unsafe { owcapi::OW_get(c_path.as_ptr(), buf_ptr, &mut buf_length) };
    if res >= 0 {
        let result = unsafe { std::slice::from_raw_parts(buf.cast::<u8>(), buf_length).to_vec() };
        unsafe {
            libc::free(buf.cast::<libc::c_void>());
        }
        Ok(result)
    } else {
        unsafe {
            libc::free(buf.cast::<libc::c_void>());
        }
        Err(Error::new(res))
    }
}

pub fn set(path: &str, value: &str) -> Result<(), Error> {
    let c_path = CString::new(path)?;
    let c_val = CString::new(value)?;
    let len_i: isize = c_val.as_bytes_with_nul().len().try_into()?;
    #[allow(clippy::cast_sign_loss)]
    let res = unsafe { owcapi::OW_put(c_path.as_ptr(), c_val.as_ptr(), len_i as usize) };
    if res == len_i {
        Ok(())
    } else {
        Err(Error::new(-2))
    }
}

pub fn set_bytes(path: &str, value: &[u8]) -> Result<(), Error> {
    let c_path = CString::new(path)?;
    let len_i: isize = value.len().try_into()?;
    #[allow(clippy::cast_sign_loss)]
    let res = unsafe {
        owcapi::OW_put(
            c_path.as_ptr(),
            value.as_ptr().cast::<c_char>(),
            len_i as usize,
        )
    };
    if res == len_i {
        Ok(())
    } else {
        Err(Error::new(-2))
    }
}

#[derive(Default)]
pub struct ScanOptions<'a> {
    types: Option<HashSet<&'a str>>,
    attrs_any: Option<HashSet<&'a str>>,
    attrs_all: Option<HashSet<&'a str>>,
}

impl<'a> ScanOptions<'a> {
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }
    #[inline]
    pub fn types(mut self, types: &'a [&'a str]) -> Self {
        self.types = Some(types.iter().copied().collect());
        self
    }
    #[inline]
    pub fn attrs_all(mut self, attrs_all: &'a [&'a str]) -> Self {
        self.attrs_all = Some(attrs_all.iter().copied().collect());
        self
    }
    #[inline]
    pub fn attrs_any(mut self, attrs_any: &'a [&'a str]) -> Self {
        self.attrs_any = Some(attrs_any.iter().copied().collect());
        self
    }
    fn matches(&self, dev: &Device) -> bool {
        if let Some(ref types) = self.types {
            if let Ok(tp) = dev.get("type") {
                if !types.contains(&tp.as_str()) {
                    return false;
                }
            } else {
                return false;
            }
        }
        let dev_attrs: Option<HashSet<&str>> =
            if self.attrs_all.is_some() || self.attrs_any.is_some() {
                Some(dev.attrs().iter().copied().collect())
            } else {
                None
            };
        if let Some(ref attrs_all) = self.attrs_all {
            let d_attrs = dev_attrs.as_ref().unwrap();
            for a in attrs_all {
                if !d_attrs.contains(a) {
                    return false;
                }
            }
        }
        if let Some(ref attrs_any) = self.attrs_any {
            let d_attrs = dev_attrs.as_ref().unwrap();
            let mut found = false;
            for a in attrs_any {
                if d_attrs.contains(a) {
                    found = true;
                }
            }
            if !found {
                return false;
            }
        }
        true
    }
}

pub fn scan(options: ScanOptions) -> Result<Vec<Device>, Error> {
    let data = get("/uncached/")?;
    let mut result = Vec::new();
    for el in data.split(',') {
        if let Some(ch) = el.chars().next() {
            if ch.is_ascii_digit() || ('A'..='F').contains(&ch) {
                if let Some(el_path) = el.strip_suffix('/') {
                    let mut dev = Device::new(el_path);
                    if dev.load().is_ok() && options.matches(&dev) {
                        result.push(dev);
                    }
                }
            }
        }
    }
    Ok(result)
}

#[inline]
fn finish() {
    unsafe { owcapi::OW_finish() };
}