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
127
128
129
130
131
132
//! ## Load objects through a local cache directory

use super::Objects;
use checksum::Checksum;
use core::errors::*;
use core::{Object, PathObject};
use hex_slice::HexSlice;
use std::fs::{self, File};
use std::io::{self, Read};
use std::path::PathBuf;
use std::time::{self, Duration};

pub struct CachedObjects<T> {
    objects_cache: PathBuf,
    missing_cache_time: Duration,
    inner: T,
}

impl<T: Objects> CachedObjects<T> {
    pub fn new(objects_cache: PathBuf, missing_cache_time: Duration, inner: T) -> CachedObjects<T> {
        CachedObjects {
            objects_cache: objects_cache,
            missing_cache_time: missing_cache_time,
            inner: inner,
        }
    }

    fn cache_path(&self, checksum: &Checksum) -> Result<PathBuf> {
        let path = self.objects_cache
            .join(format!("{}", HexSlice::new(&checksum[0..1])));
        let path = path.join(format!("{}", HexSlice::new(&checksum[1..2])));
        Ok(path.join(format!("{}", HexSlice::new(&checksum))))
    }

    /// Get the path to the missing file cache.
    fn missing_path(&self, checksum: &Checksum) -> Result<PathBuf> {
        Ok(self.objects_cache
            .join("missing")
            .join(format!("{}", HexSlice::new(checksum))))
    }

    /// Check if there is a local missing cached file, and assume that the remote file is missing
    /// if it is present, or younger than `missing_cache_time`.
    ///
    /// Returns Some(cache_path) if the file might exist.
    fn check_missing(&self, checksum: &Checksum) -> Result<(bool, PathBuf)> {
        let path = self.missing_path(checksum)?;

        match fs::metadata(&path) {
            Err(e) => {
                if e.kind() != io::ErrorKind::NotFound {
                    return Err(e.into());
                }
            }
            Ok(m) => {
                let now = time::SystemTime::now();
                let age = now.duration_since(m.modified()?)?;

                let expires = self.missing_cache_time
                    .checked_sub(age)
                    .unwrap_or_else(|| Duration::new(0, 0));

                debug!(
                    "cache: missing file exists: {} (age: {}s, expires: {}s)",
                    path.display(),
                    age.as_secs(),
                    expires.as_secs()
                );

                // remote file is expected to be missing
                if age < self.missing_cache_time {
                    return Ok((true, path));
                }

                debug!("cache: removing missing entry: {}", path.display());
                fs::remove_file(&path)?;
            }
        }

        Ok((false, path))
    }
}

impl<T: Objects> Objects for CachedObjects<T> {
    fn put_object(&mut self, checksum: &Checksum, source: &mut Read, force: bool) -> Result<()> {
        self.inner.put_object(checksum, source, force)
    }

    fn get_object(&mut self, checksum: &Checksum) -> Result<Option<Box<Object>>> {
        let cache_path = self.cache_path(checksum)?;

        if cache_path.is_file() {
            return Ok(Some(Box::new(PathObject::new(None, cache_path))));
        }

        let (missing, missing_path) = self.check_missing(checksum)?;

        if missing {
            return Ok(None);
        }

        let out = self.inner.get_object(checksum)?;

        if let Some(object) = out {
            if let Some(parent) = cache_path.parent() {
                if !parent.is_dir() {
                    fs::create_dir_all(parent)?;
                }
            }

            io::copy(&mut object.read()?, &mut File::create(cache_path)?)?;
            return Ok(Some(object));
        } else {
            // write cache entry indicating that there is nothing in the remote entry to avoid
            // subsequent requests.
            debug!(
                "cache: creating missing cache entry: {}",
                missing_path.display()
            );

            if let Some(parent) = missing_path.parent() {
                if !parent.is_dir() {
                    fs::create_dir_all(parent)?;
                }
            }

            File::create(missing_path)?;
        }

        return Ok(None);
    }
}