use std::borrow::Cow;
use std::fs::File;
use std::io::Result;
use std::path::Path;
use std::path::PathBuf;
use crate::cache_dir::CacheDir;
use crate::trigger::PeriodicTrigger;
use crate::KISMET_TEMPORARY_SUBDIRECTORY as TEMP_SUBDIR;
const MAINTENANCE_SCALE: usize = 3;
#[derive(Clone, Debug)]
pub struct Cache {
temp_dir: PathBuf,
trigger: PeriodicTrigger,
capacity: usize,
}
impl CacheDir for Cache {
#[inline]
fn temp_dir(&self) -> Cow<Path> {
Cow::from(&self.temp_dir)
}
#[inline]
fn base_dir(&self) -> Cow<Path> {
Cow::from(self.temp_dir.parent().unwrap_or(&self.temp_dir))
}
#[inline]
fn trigger(&self) -> &PeriodicTrigger {
&self.trigger
}
#[inline]
fn capacity(&self) -> usize {
self.capacity
}
}
impl Cache {
pub fn new(base_dir: PathBuf, capacity: usize) -> Cache {
let mut temp_dir = base_dir;
temp_dir.push(TEMP_SUBDIR);
Cache {
temp_dir,
trigger: PeriodicTrigger::new((capacity / MAINTENANCE_SCALE) as u64),
capacity,
}
}
pub fn get(&self, name: &str) -> Result<Option<File>> {
CacheDir::get(self, name)
}
pub fn temp_dir(&self) -> Result<Cow<Path>> {
CacheDir::ensure_temp_dir(self)
}
pub fn set(&self, name: &str, value: &Path) -> Result<()> {
CacheDir::set(self, name, value)?;
Ok(())
}
pub fn put(&self, name: &str, value: &Path) -> Result<()> {
CacheDir::put(self, name, value)?;
Ok(())
}
pub fn touch(&self, name: &str) -> Result<bool> {
CacheDir::touch(self, name)
}
}
#[test]
fn smoke_test() {
use tempfile::NamedTempFile;
use test_dir::{DirBuilder, FileType, TestDir};
const PAYLOAD_MULTIPLIER: usize = 13;
let temp = TestDir::temp()
.create(TEMP_SUBDIR, FileType::Dir)
.create(&format!("{}/garbage", TEMP_SUBDIR), FileType::ZeroFile(10));
assert!(std::fs::metadata(temp.path(&format!("{}/garbage", TEMP_SUBDIR))).is_ok());
std::thread::sleep(std::time::Duration::from_secs_f64(2.5));
let cache = Cache::new(temp.path("."), 10);
for i in 0..20 {
let name = format!("{}", i);
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
std::fs::write(tmp.path(), format!("{}", PAYLOAD_MULTIPLIER * i))
.expect("write must succeed");
cache.put(&name, tmp.path()).expect("put must succeed");
}
let present: usize = (0..20)
.map(|i| {
let name = format!("{}", i);
match cache.get(&name).expect("get must succeed") {
Some(mut file) => {
use std::io::Read;
let mut buf = Vec::new();
file.read_to_end(&mut buf).expect("read must succeed");
assert_eq!(buf, format!("{}", PAYLOAD_MULTIPLIER * i).into_bytes());
1
}
None => 0,
}
})
.sum();
assert!(present >= 10);
assert!(present < 20);
assert!(
matches!(std::fs::metadata(temp.path(&format!("{}/garbage", TEMP_SUBDIR))),
Err(e) if e.kind() == std::io::ErrorKind::NotFound)
);
}
#[test]
fn test_set() {
use std::io::{Read, Write};
use tempfile::NamedTempFile;
use test_dir::{DirBuilder, TestDir};
let temp = TestDir::temp();
let cache = Cache::new(temp.path("."), 1);
{
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
tmp.as_file().write_all(b"v1").expect("write must succeed");
cache
.set("entry", tmp.path())
.expect("initial set must succeed");
}
{
let mut cached = cache
.get("entry")
.expect("must succeed")
.expect("must be found");
let mut dst = Vec::new();
cached.read_to_end(&mut dst).expect("read must succeed");
assert_eq!(&dst, b"v1");
}
{
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
tmp.as_file().write_all(b"v2").expect("write must succeed");
cache
.set("entry", tmp.path())
.expect("overwrite must succeed");
}
{
let mut cached = cache
.get("entry")
.expect("must succeed")
.expect("must be found");
let mut dst = Vec::new();
cached.read_to_end(&mut dst).expect("read must succeed");
assert_eq!(&dst, b"v2");
}
}
#[test]
fn test_put() {
use std::io::{Read, Write};
use tempfile::NamedTempFile;
use test_dir::{DirBuilder, TestDir};
let temp = TestDir::temp();
let cache = Cache::new(temp.path("."), 1);
{
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
tmp.as_file().write_all(b"v1").expect("write must succeed");
cache
.put("entry", tmp.path())
.expect("initial set must succeed");
}
{
let mut cached = cache
.get("entry")
.expect("must succeed")
.expect("must be found");
let mut dst = Vec::new();
cached.read_to_end(&mut dst).expect("read must succeed");
assert_eq!(&dst, b"v1");
}
{
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
tmp.as_file().write_all(b"v2").expect("write must succeed");
cache
.put("entry", tmp.path())
.expect("overwrite must succeed");
}
{
let mut cached = cache
.get("entry")
.expect("must succeed")
.expect("must be found");
let mut dst = Vec::new();
cached.read_to_end(&mut dst).expect("read must succeed");
assert_eq!(&dst, b"v1");
}
}
#[test]
fn test_touch() {
use tempfile::NamedTempFile;
use test_dir::{DirBuilder, TestDir};
let temp = TestDir::temp();
let cache = Cache::new(temp.path("."), 5);
for i in 0..15 {
let name = format!("{}", i);
assert_eq!(cache.touch("0").expect("touch must not fail"), i > 0);
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
cache.put(&name, tmp.path()).expect("put must succeed");
if i == 0 {
std::thread::sleep(std::time::Duration::from_secs_f64(1.5));
}
}
cache.get("0").expect("must succed").expect("must be found");
}
#[test]
fn test_recent_temp_file() {
use tempfile::NamedTempFile;
use test_dir::{DirBuilder, FileType, TestDir};
let temp = TestDir::temp()
.create(TEMP_SUBDIR, FileType::Dir)
.create(&format!("{}/garbage", TEMP_SUBDIR), FileType::ZeroFile(10));
assert!(std::fs::metadata(temp.path(&format!("{}/garbage", TEMP_SUBDIR))).is_ok());
let cache = Cache::new(temp.path("."), 1);
for i in 0..2 {
let tmp = NamedTempFile::new_in(cache.temp_dir().expect("temp_dir must succeed"))
.expect("new temp file must succeed");
cache
.put(&format!("{}", i), tmp.path())
.expect("put must succeed");
}
assert!(std::fs::metadata(temp.path(&format!("{}/garbage", TEMP_SUBDIR))).is_ok());
}