vin-decode 0.1.10

Auto-updating VIN decoder backed by NHTSA vPIC, refreshed monthly via CI
Documentation
#![allow(missing_docs, dead_code)]

use std::fs::File;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};

use fst::{Map, Set, Streamer};
use memmap2::Mmap;
use rkyv::rancor::Error as RkyvError;
use rkyv::vec::ArchivedVec;

use crate::Error;
use crate::data::{RkyvDe, RkyvSer, Saveable};

pub struct FstMap<T>
where
    T: RkyvSer,
    T::Archived: RkyvDe<T>,
{
    fst: Map<Mmap>,
    values: Mmap,
    _t: PhantomData<fn() -> T>,
}

impl<T> FstMap<T>
where
    T: RkyvSer + Saveable,
    T::Archived: RkyvDe<T>,
{
    pub fn open(dir: &Path) -> crate::Result<Self> {
        Self::open_paths(
            &dir.join(format!("{}.fst", T::base_name())),
            &dir.join(format!("{}.bin", T::base_name())),
        )
    }
}

impl<T> FstMap<T>
where
    T: RkyvSer,
    T::Archived: RkyvDe<T>,
{
    pub fn open_paths(fst_path: &Path, values_path: &Path) -> crate::Result<Self> {
        let fst_file =
            File::open(fst_path).map_err(|_| Error::MissingData(fst_path.display().to_string()))?;
        let values_file = File::open(values_path)
            .map_err(|_| Error::MissingData(values_path.display().to_string()))?;
        let fst_mmap = unsafe { Mmap::map(&fst_file)? };
        let values = unsafe { Mmap::map(&values_file)? };
        let fst = Map::new(fst_mmap).map_err(|e| Error::MissingData(e.to_string()))?;
        Ok(FstMap {
            fst,
            values,
            _t: PhantomData,
        })
    }

    pub fn get(&self, key: &str) -> Option<Vec<T>> {
        let combined = self.fst.get(key)?;
        let offset = (combined >> 32) as usize;
        let len = (combined & 0xFFFF_FFFF) as usize;
        let bytes = &self.values[offset..offset + len];
        let archived = unsafe { rkyv::access_unchecked::<ArchivedVec<T::Archived>>(bytes) };
        rkyv::deserialize::<Vec<T>, RkyvError>(archived).ok()
    }

    pub fn keys(&self) -> Vec<String> {
        let mut out = Vec::new();
        let mut stream = self.fst.stream();
        while let Some((k, _)) = stream.next() {
            out.push(String::from_utf8_lossy(k).into_owned());
        }
        out
    }

    pub fn contains_key(&self, key: &str) -> bool {
        self.fst.contains_key(key)
    }

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

    pub fn is_empty(&self) -> bool {
        self.fst.is_empty()
    }
}

pub struct FstSet {
    set: Set<Mmap>,
}

impl FstSet {
    pub fn open(fst_path: &Path) -> crate::Result<Self> {
        let f =
            File::open(fst_path).map_err(|_| Error::MissingData(fst_path.display().to_string()))?;
        let mmap = unsafe { Mmap::map(&f)? };
        let set = Set::new(mmap).map_err(|e| Error::MissingData(e.to_string()))?;
        Ok(FstSet { set })
    }

    pub fn keys(&self) -> Vec<String> {
        let mut out = Vec::new();
        let mut stream = self.set.stream();
        while let Some(k) = stream.next() {
            out.push(String::from_utf8_lossy(k).into_owned());
        }
        out
    }

    pub fn contains(&self, key: &str) -> bool {
        self.set.contains(key)
    }

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

    pub fn is_empty(&self) -> bool {
        self.set.is_empty()
    }
}

pub fn data_dir() -> PathBuf {
    if let Ok(p) = std::env::var("VIN_DECODE_DATA_DIR") {
        return PathBuf::from(p);
    }
    if let Some(home) = std::env::var_os("HOME") {
        return PathBuf::from(home).join(".vin-decode-cache");
    }
    PathBuf::from(".vin-decode-cache")
}