mdict-rs 0.1.4

Library-first Rust parser for MDict .mdx and .mdd dictionaries
Documentation
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use std::sync::Mutex;

use crate::error::{Error, Result};

pub struct FileSource {
    path: PathBuf,
    file: Mutex<File>,
    len: u64,
}

impl std::fmt::Debug for FileSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FileSource")
            .field("path", &self.path)
            .field("len", &self.len)
            .finish()
    }
}

impl FileSource {
    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
        let path = path.as_ref();
        let file = File::open(path)?;
        let len = file.metadata()?.len();
        if len == 0 {
            return Err(Error::InvalidFormat("empty input file"));
        }
        Ok(Self {
            path: path.to_path_buf(),
            file: Mutex::new(file),
            len,
        })
    }

    pub fn path(&self) -> &Path {
        &self.path
    }

    pub fn len(&self) -> u64 {
        self.len
    }

    pub fn read_exact_at(&self, offset: u64, len: usize, context: &'static str) -> Result<Vec<u8>> {
        let end = offset
            .checked_add(len as u64)
            .ok_or(Error::InvalidFormat("source range overflow"))?;
        if end > self.len {
            return Err(Error::truncated(
                context,
                len,
                self.len.saturating_sub(offset) as usize,
            ));
        }

        let mut file = self
            .file
            .lock()
            .map_err(|_| Error::InvalidFormat("source mutex poisoned"))?;
        file.seek(SeekFrom::Start(offset))?;
        let mut out = vec![0; len];
        file.read_exact(&mut out)?;
        Ok(out)
    }
}