use std::sync::atomic::{AtomicU8, Ordering};
use crate::FileMetadata;
const INITIALIZED: u8 = 1 << 0;
const EXISTS: u8 = 1 << 1;
const IS_FILE: u8 = 1 << 2;
const IS_DIR: u8 = 1 << 3;
const IS_SYMLINK: u8 = 1 << 4;
#[derive(Default)]
pub struct CachedMeta {
link: AtomicU8,
followed: AtomicU8,
}
impl CachedMeta {
pub const fn new() -> Self {
Self { link: AtomicU8::new(0), followed: AtomicU8::new(0) }
}
pub fn link_or_init<F: FnOnce() -> Option<FileMetadata>>(&self, f: F) -> Option<FileMetadata> {
get_or_init(&self.link, f)
}
pub fn followed_or_init<F: FnOnce() -> Option<FileMetadata>>(
&self,
f: F,
) -> Option<FileMetadata> {
get_or_init(&self.followed, f)
}
}
fn get_or_init<F: FnOnce() -> Option<FileMetadata>>(slot: &AtomicU8, f: F) -> Option<FileMetadata> {
let bits = slot.load(Ordering::Relaxed);
if (bits & INITIALIZED) != 0 {
return decode(bits);
}
let meta = f();
slot.store(encode(meta), Ordering::Relaxed);
meta
}
fn encode(meta: Option<FileMetadata>) -> u8 {
let Some(meta) = meta else { return INITIALIZED };
let mut bits = INITIALIZED | EXISTS;
if meta.is_file() {
bits |= IS_FILE;
}
if meta.is_dir() {
bits |= IS_DIR;
}
if meta.is_symlink() {
bits |= IS_SYMLINK;
}
bits
}
fn decode(bits: u8) -> Option<FileMetadata> {
((bits & EXISTS) != 0).then(|| {
FileMetadata::new((bits & IS_FILE) != 0, (bits & IS_DIR) != 0, (bits & IS_SYMLINK) != 0)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn parts(meta: FileMetadata) -> (bool, bool, bool) {
(meta.is_file(), meta.is_dir(), meta.is_symlink())
}
#[test]
fn is_two_bytes() {
assert_eq!(std::mem::size_of::<CachedMeta>(), 2);
}
#[test]
fn roundtrips_and_caches() {
let cases = [
None,
Some(FileMetadata::new(false, false, false)),
Some(FileMetadata::new(true, false, false)),
Some(FileMetadata::new(false, true, false)),
Some(FileMetadata::new(false, false, true)),
];
for expected in cases {
let meta = CachedMeta::new();
assert_eq!(meta.link_or_init(|| expected).map(parts), expected.map(parts));
assert_eq!(
meta.link_or_init(|| panic!("must not be called")).map(parts),
expected.map(parts)
);
}
}
#[test]
fn link_and_followed_are_separate_slots() {
let meta = CachedMeta::new();
meta.link_or_init(|| Some(FileMetadata::new(false, false, true)));
meta.followed_or_init(|| Some(FileMetadata::new(false, true, false)));
assert_eq!(meta.link_or_init(|| panic!()).map(parts), Some((false, false, true)));
assert_eq!(meta.followed_or_init(|| panic!()).map(parts), Some((false, true, false)));
}
}