use crate::CacheDigest;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FileIdentity {
pub path: PathBuf,
pub len: u64,
pub modified: SystemTime,
pub changed: Option<(i64, i64)>,
}
impl FileIdentity {
pub fn describe(path: &Path, metadata: &std::fs::Metadata) -> Option<Self> {
Some(Self {
path: path.to_path_buf(),
len: metadata.len(),
modified: metadata.modified().ok()?,
changed: change_token(metadata),
})
}
pub fn still_describes(&self) -> std::io::Result<bool> {
let metadata = std::fs::metadata(&self.path)?;
Ok(Self::describe(&self.path, &metadata).as_ref() == Some(self))
}
}
#[cfg(unix)]
fn change_token(metadata: &std::fs::Metadata) -> Option<(i64, i64)> {
use std::os::unix::fs::MetadataExt;
Some((metadata.ctime(), metadata.ctime_nsec()))
}
#[cfg(not(unix))]
fn change_token(_metadata: &std::fs::Metadata) -> Option<(i64, i64)> {
None
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RecordedFileDigest {
pub file: FileIdentity,
pub digest: CacheDigest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FileDigestScope {
Content,
CcInput,
}
pub trait FileDigestCache: Send + Sync {
fn find(&self, scope: FileDigestScope, files: &[FileIdentity]) -> Vec<Option<CacheDigest>>;
fn record(&self, scope: FileDigestScope, entries: Vec<RecordedFileDigest>);
}
pub struct NoFileDigestCache;
impl FileDigestCache for NoFileDigestCache {
fn find(&self, _scope: FileDigestScope, files: &[FileIdentity]) -> Vec<Option<CacheDigest>> {
vec![None; files.len()]
}
fn record(&self, _scope: FileDigestScope, _entries: Vec<RecordedFileDigest>) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_identity_describes_the_file_until_it_is_written_or_removed() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("input.rs");
std::fs::write(&path, b"fn main() {}").unwrap();
let identity = FileIdentity::describe(&path, &std::fs::metadata(&path).unwrap()).unwrap();
assert!(identity.still_describes().unwrap());
std::thread::sleep(std::time::Duration::from_millis(20));
std::fs::write(&path, b"fn main() { }").unwrap();
assert!(!identity.still_describes().unwrap());
std::fs::remove_file(&path).unwrap();
assert!(identity.still_describes().is_err());
}
}