grass-driver 0.1.4

Driver for GRASS
Documentation
#![allow(dead_code, unused_variables)]

use std::{
    io::{Result, Seek, SeekFrom},
    path::{Path, PathBuf},
};

use file_lock::{FileLock, FileOptions};
use serde::{Deserialize, Serialize};

use super::config::CacheConfig;

#[derive(Serialize, Deserialize, Default)]
pub(crate) struct CacheState {
    disk_space_used: u64,
    cache_state: Vec<String>,
    #[serde(skip)]
    cache_root: PathBuf,
    #[serde(skip)]
    lock: Option<FileLock>,
    #[serde(skip)]
    config: CacheConfig,
}

impl CacheState {
    const STATE_FILE_NAME: &'static str = "cache_state.json";
    fn lock_cache_state<P: AsRef<Path>>(root: &P) -> Result<FileLock> {
        let mut cache_state_file = root.as_ref().to_path_buf();
        cache_state_file.push(Self::STATE_FILE_NAME);
        let lock_opt = FileOptions::new().read(true).write(true);
        Ok(FileLock::lock(&cache_state_file, true, lock_opt)?)
    }

    fn compose_binary_path(&self, hash: &str) -> PathBuf {
        let mut binary_file = self.cache_root.clone();
        binary_file.push(&hash[..2]);
        binary_file.push(&hash);
        binary_file
    }

    fn write_state_file(&mut self) -> Result<()> {
        let mut lock = self.lock.take().unwrap();
        lock.file.set_len(0)?;
        lock.file.seek(SeekFrom::Start(0))?;
        serde_json::to_writer(&mut lock.file, self)?;
        self.lock = Some(lock);
        Ok(())
    }

    pub fn load_cache<P: AsRef<Path>>(root: &P) -> Result<Self> {
        let mut state_path = root.as_ref().to_path_buf();
        state_path.push(Self::STATE_FILE_NAME);

        if !root.as_ref().exists() || !state_path.exists() {
            std::fs::DirBuilder::new().recursive(true).create(root)?;
            serde_json::to_writer(std::fs::File::create(state_path)?, &Self::default())?;
        }

        let mut lock = Self::lock_cache_state(root)?;
        let mut ret: CacheState = serde_json::from_reader(&mut lock.file)?;
        ret.lock = Some(lock);
        ret.cache_root = root.as_ref().to_path_buf();

        let mut config_path = root.as_ref().to_path_buf();
        config_path.push("config.json");
        ret.config = CacheConfig::load_cache_config(&config_path).unwrap_or_default();

        Ok(ret)
    }

    pub fn query_cache_entry(&mut self, hash_code: &str, buf: &mut PathBuf) -> Result<bool> {
        let binary_file = self.compose_binary_path(hash_code);
        if binary_file.exists() {
            if let Some((mut idx, _)) = {
                self.cache_state
                    .iter()
                    .enumerate()
                    .find(|(_, current)| hash_code == current.as_str())
            } {
                while idx > 0 {
                    self.cache_state.swap(idx, idx - 1);
                    idx -= 1;
                }
            }
            self.write_state_file()?;
            *buf = binary_file;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    pub fn update_cache<BuildFn: FnOnce(&mut PathBuf) -> Result<()>>(
        &mut self,
        hash_code: &str,
        build: BuildFn,
        buf: &mut PathBuf,
    ) -> Result<()> {
        let mut artifact_path = PathBuf::new();
        build(&mut artifact_path)?;

        let file_size = std::fs::metadata(&artifact_path)?.len();
        let size_limit = self.config.disk_space_limit();

        while size_limit < file_size + self.disk_space_used {
            if let Some(victim) = self.cache_state.pop() {
                let path = self.compose_binary_path(&victim);
                let victim_size = std::fs::metadata(&path)?.len();
                self.disk_space_used -= victim_size;
            } else {
                break;
            }
        }

        let binary_path = self.compose_binary_path(hash_code);

        if let Some(parent) = binary_path.parent() {
            std::fs::DirBuilder::new().recursive(true).create(parent)?;
        }

        std::fs::copy(artifact_path.as_path(), binary_path.as_path())?;

        self.cache_state.push(hash_code.to_string());

        let mut idx = self.cache_state.len() - 1;
        while idx > 0 {
            self.cache_state.swap(idx - 1, idx);
            idx -= 1;
        }

        self.disk_space_used += file_size;

        self.write_state_file()?;

        *buf = binary_path;

        Ok(())
    }
}