1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::fs::File;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;

use crate::crc_any::CRCu64;
use crate::lru_time_cache::LruCache;
use crate::EntityTag;

#[inline]
fn compute_file_etag<P: AsRef<Path>>(path: P) -> Result<EntityTag, io::Error> {
    let mut crc64ecma = CRCu64::crc64();

    let mut buffer = [0u8; 4096];

    {
        let mut file = File::open(path.as_ref())?;

        loop {
            match file.read(&mut buffer) {
                Ok(c) => {
                    if c == 0 {
                        break;
                    }
                    crc64ecma.digest(&buffer[0..c]);
                }
                Err(error) => {
                    return Err(error);
                }
            }
        }
    }

    let crc64 = crc64ecma.get_crc();

    Ok(EntityTag::new(true, format!("{:X}", crc64)))
}

#[derive(Educe)]
#[educe(Debug)]
#[allow(clippy::type_complexity)]
pub struct FileEtagCache {
    #[educe(Debug(ignore))]
    cache_table: Mutex<LruCache<PathBuf, (Arc<EntityTag>, Option<SystemTime>)>>,
}

impl FileEtagCache {
    #[inline]
    /// Create an instance of `EtagCache`.
    pub fn new(cache_capacity: usize) -> FileEtagCache {
        FileEtagCache {
            cache_table: Mutex::new(LruCache::with_capacity(cache_capacity)),
        }
    }

    #[inline]
    /// Clear cache.
    pub fn clear_cache(&self) {
        self.cache_table.lock().unwrap().clear();
    }

    #[inline]
    /// Check if a cache key exists.
    pub fn contains_key<S: AsRef<Path>>(&self, key: S) -> bool {
        self.cache_table.lock().unwrap().get(key.as_ref()).is_some()
    }

    #[inline]
    /// Get an Etag with a name as its key.
    pub fn get_or_insert<P: AsRef<Path> + Into<PathBuf>>(
        &self,
        path: P,
    ) -> io::Result<Arc<EntityTag>> {
        let path_ref = path.as_ref();

        let mtime = match self
            .cache_table
            .lock()
            .unwrap()
            .get(path_ref)
            .map(|(etag, mtime)| (etag.clone(), *mtime))
        {
            Some((etag, mtime)) => {
                let metadata = path_ref.metadata()?;

                match mtime {
                    Some(mtime) => {
                        match metadata.modified() {
                            Ok(new_mtime) => {
                                if new_mtime != mtime {
                                    Some(new_mtime)
                                } else {
                                    return Ok(etag);
                                }
                            }
                            Err(_) => None,
                        }
                    }
                    None => {
                        match metadata.modified() {
                            Ok(new_mtime) => Some(new_mtime),
                            Err(_) => None,
                        }
                    }
                }
            }
            None => {
                let metadata = path_ref.metadata()?;

                match metadata.modified() {
                    Ok(new_mtime) => Some(new_mtime),
                    Err(_) => None,
                }
            }
        };

        let etag = compute_file_etag(&path)?;

        let etag = Arc::new(etag);

        self.cache_table.lock().unwrap().insert(path.into(), (etag.clone(), mtime));

        Ok(etag)
    }
}