uci_rs 0.1.2

an openwrt's UCI (Unified Configuration Interface) parser and serializer.
Documentation
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::fs::{self, create_dir_all, File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::iter::repeat_with;
use std::mem;
use std::path::{Path, PathBuf};

use std::os::unix::fs::OpenOptionsExt;

use crate::utils::{PathError, PersistError};

use super::Result;

const NUM_RETRIES: u32 = 5;
const NUM_RAND_CHARS: usize = 6;

fn create_named(mut path: PathBuf, open_options: &mut OpenOptions) -> io::Result<TempFile> {
    if !path.is_absolute() {
        path = env::current_dir()?.join(path)
    }

    open_options.read(true).write(true).create_new(true);
    open_options.mode(0o644);
    open_options.open(&path).map(|file| TempFile {
        path: path.into_boxed_path(),
        file,
    })
}

fn persist(old_path: &Path, new_path: &Path, overwrite: bool) -> io::Result<()> {
    if overwrite {
        println!("{:?} {:?}", old_path, new_path);
        fs::rename(old_path, new_path)?;
    } else {
        fs::hard_link(old_path, new_path)?;
        fs::remove_file(old_path)?;
    }
    Ok(())
}

#[derive(Clone, Eq, PartialEq)]
pub struct TempFile<F = File> {
    pub path: Box<Path>,
    pub file: F,
}

impl TempFile<File> {
    const RANDOM_LEN: usize = NUM_RAND_CHARS;
    const SUFFIX: &'static str = ".tmp";
    const APPEND: bool = false;
    pub fn new<P: AsRef<Path>>(dir: P, prefix: String) -> io::Result<TempFile> {
        let num_retries = if Self::RANDOM_LEN != 0 {
            NUM_RETRIES
        } else {
            1
        };

        for _ in 0..num_retries {
            let path = dir.as_ref().join(Self::tmp_name(&prefix));
            return match create_named(path, OpenOptions::new().append(Self::APPEND)) {
                Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
                Err(ref e) if e.kind() == io::ErrorKind::AddrInUse => continue,
                Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
                    create_dir_all(&dir)?;
                    continue;
                }
                res => res,
            };
        }

        Err(io::Error::new(
            io::ErrorKind::AlreadyExists,
            "too many temporary files exist",
        ))
    }

    fn tmp_name(prefix: &str) -> OsString {
        let mut buf = OsString::with_capacity(prefix.len() + Self::SUFFIX.len() + Self::RANDOM_LEN);
        buf.push(prefix);
        buf.push("_");
        let mut char_buf = [0u8; 4];
        for c in repeat_with(fastrand::alphanumeric).take(Self::RANDOM_LEN) {
            buf.push(c.encode_utf8(&mut char_buf));
        }
        buf.push(Self::SUFFIX);
        buf
    }
}

impl<F> TempFile<F> {
    pub fn close(mut self) -> Result<()> {
        let result = fs::remove_file(&self.path).map_err(|err| PathError {
            path: self.path.clone().into(),
            error: err,
        })?;
        self.path = PathBuf::new().into_boxed_path();
        mem::forget(self);
        Ok(result)
    }

    pub fn persist<P: AsRef<Path>>(mut self, new_path: P) -> Result<()> {
        let TempFile { ref path, ref file } = self;
        match persist(&self.path, new_path.as_ref(), true) {
            Ok(_) => {
                self.path = PathBuf::new().into_boxed_path();
                mem::forget(self);
                Ok(())
            }
            Err(error) => Err(PersistError {
                file: TempFile {
                    path: path.clone(),
                    file,
                },
                error,
            }
            .into()),
        }
    }

    pub fn as_file(&self) -> &F {
        &self.file
    }

    pub fn as_file_mut(&mut self) -> &mut F {
        &mut self.file
    }
}

impl<F: Read> Read for TempFile<F> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.as_file_mut().read(buf).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<'a, F> Read for &'a TempFile<F>
where
    &'a F: Read,
{
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.as_file().read(buf).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<F: Write> Write for TempFile<F> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.as_file_mut().write(buf).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
    #[inline]
    fn flush(&mut self) -> io::Result<()> {
        self.as_file_mut().flush().map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<'a, F> Write for &'a TempFile<F>
where
    &'a F: Write,
{
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.as_file().write(buf).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
    #[inline]
    fn flush(&mut self) -> io::Result<()> {
        self.as_file().flush().map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<F: Seek> Seek for TempFile<F> {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        self.as_file_mut().seek(pos).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<'a, F> Seek for &'a TempFile<F>
where
    &'a F: Seek,
{
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        self.as_file().seek(pos).map_err(|error| {
            PathError {
                path: self.path.clone().into_path_buf(),
                error,
            }
            .into()
        })
    }
}

impl<F> std::os::unix::io::AsRawFd for TempFile<F>
where
    F: std::os::unix::io::AsRawFd,
{
    #[inline]
    fn as_raw_fd(&self) -> std::os::unix::io::RawFd {
        self.as_file().as_raw_fd()
    }
}

impl<F> Drop for TempFile<F> {
    fn drop(&mut self) {
        let _ = fs::remove_file(&self.path);
    }
}

impl<F> fmt::Debug for TempFile<F> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "TempFile({:?})", self.path)
    }
}

impl<F> AsRef<Path> for TempFile<F> {
    #[inline]
    fn as_ref(&self) -> &Path {
        &self.path
    }
}