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
|
use std::env;
use std::io::Write;
use std::path::{Path, PathBuf};
use crate::error::{CacheError, Error};
use crate::parse::parse_binary;
use crate::syntax::{binary, Hash};
use crate::{Ctxt, Typed};
use std::ffi::OsStr;
use std::fs::File;
#[cfg(any(unix, windows))]
const CACHE_ENV_VAR: &str = "XDG_CACHE_HOME";
#[cfg(unix)]
const ALTERNATE_CACHE_ENV_VAR: &str = "HOME";
#[cfg(windows)]
const ALTERNATE_CACHE_ENV_VAR: &str = "LOCALAPPDATA";
#[cfg(any(unix, windows))]
fn default_cache_dir() -> Result<PathBuf, CacheError> {
let cache_base_path = match env::var(OsStr::new(CACHE_ENV_VAR)) {
Ok(path) => PathBuf::from(path),
Err(_) => match env::var(OsStr::new(ALTERNATE_CACHE_ENV_VAR)) {
Ok(path) => PathBuf::from(path).join(".cache"),
Err(_) => return Err(CacheError::MissingConfiguration),
},
};
Ok(cache_base_path.join("dhall"))
}
#[cfg(not(any(unix, windows)))]
fn default_cache_dir() -> Result<PathBuf, CacheError> {
Err(CacheError::MissingConfiguration)
}
#[derive(Debug, Clone, PartialEq)]
pub struct Cache {
cache_dir: PathBuf,
}
impl Cache {
pub fn new() -> Result<Cache, Error> {
let cache_dir = default_cache_dir()?;
if !cache_dir.exists() {
std::fs::create_dir_all(&cache_dir)
.map_err(|e| CacheError::InitialisationError { cause: e })?;
}
Ok(Cache { cache_dir })
}
fn entry_path(&self, hash: &Hash) -> PathBuf {
self.cache_dir.join(filename_for_hash(hash))
}
pub fn get<'cx>(
&self,
cx: Ctxt<'cx>,
hash: &Hash,
) -> Result<Typed<'cx>, Error> {
let path = self.entry_path(hash);
let res = read_cache_file(cx, &path, hash);
if res.is_err() && path.exists() {
// Delete cache file since it's invalid. We ignore the error.
let _ = std::fs::remove_file(&path);
}
res
}
pub fn insert<'cx>(
&self,
cx: Ctxt<'cx>,
hash: &Hash,
expr: &Typed<'cx>,
) -> Result<(), Error> {
let path = self.entry_path(hash);
write_cache_file(cx, &path, expr)
}
}
/// Read a file from the cache, also checking that its hash is valid.
fn read_cache_file<'cx>(
cx: Ctxt<'cx>,
path: &Path,
hash: &Hash,
) -> Result<Typed<'cx>, Error> {
let data = crate::utils::read_binary_file(path)?;
match hash {
Hash::SHA256(hash) => {
let actual_hash = crate::utils::sha256_hash(&data);
if hash[..] != actual_hash[..] {
return Err(CacheError::CacheHashInvalid.into());
}
}
}
Ok(parse_binary(&data)?.resolve(cx)?.typecheck(cx)?)
}
/// Write a file to the cache.
fn write_cache_file<'cx>(
cx: Ctxt<'cx>,
path: &Path,
expr: &Typed<'cx>,
) -> Result<(), Error> {
let data = binary::encode(&expr.to_expr(cx))?;
File::create(path)?.write_all(data.as_slice())?;
Ok(())
}
fn filename_for_hash(hash: &Hash) -> String {
match hash {
Hash::SHA256(sha) => format!("1220{}", hex::encode(&sha)),
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::syntax::parse_expr;
#[test]
fn filename_for_hash_should_work() {
let hash =
Hash::SHA256(parse_expr("1").unwrap().sha256_hash().unwrap());
assert_eq!("1220d60d8415e36e86dae7f42933d3b0c4fe3ca238f057fba206c7e9fbf5d784fe15".to_string(), filename_for_hash(&hash));
}
}
|