use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, MutexGuard};
use crate::document::Document;
pub fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
mutex
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
const BUDGET: usize = 32 * 1024 * 1024;
const MAX_DOCUMENT: usize = 1024 * 1024;
#[derive(Debug, Default)]
pub struct ReadMemo {
depth: usize,
docs: HashMap<PathBuf, (String, Document)>,
bytes: usize,
}
impl ReadMemo {
pub(crate) fn enter(&mut self) {
self.depth += 1;
}
pub(crate) fn leave(&mut self) {
self.depth = self.depth.saturating_sub(1);
if self.depth == 0 {
self.clear();
}
}
pub(crate) fn get(&self, path: &Path) -> Option<(String, Document)> {
if self.depth == 0 {
return None;
}
self.docs.get(path).cloned()
}
pub(crate) fn remember(&mut self, path: &Path, text: &str, doc: &Document) {
if self.depth == 0 || text.len() > MAX_DOCUMENT || self.bytes + text.len() > BUDGET {
return;
}
self.bytes += text.len();
self.docs
.insert(path.to_path_buf(), (text.to_string(), doc.clone()));
}
pub fn forget(&mut self, path: &Path) {
if let Some((text, _)) = self.docs.remove(path) {
self.bytes -= text.len();
}
}
pub(crate) fn clear(&mut self) {
self.docs.clear();
self.bytes = 0;
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.docs.len()
}
}
#[must_use = "a read scope ends the moment its guard is dropped"]
pub struct ReadScope(Arc<Mutex<ReadMemo>>);
impl ReadScope {
pub(crate) fn open(memo: &Arc<Mutex<ReadMemo>>) -> Self {
lock(memo).enter();
ReadScope(Arc::clone(memo))
}
}
impl Drop for ReadScope {
fn drop(&mut self) {
lock(&self.0).leave();
}
}
impl std::fmt::Debug for ReadScope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReadScope")
.field("depth", &lock(&self.0).depth)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn doc(text: &str) -> Document {
Document::parse("a.md", text).unwrap()
}
#[test]
fn nothing_is_remembered_outside_a_scope() {
let mut memo = ReadMemo::default();
memo.remember(Path::new("a.md"), "hello", &doc("hello"));
assert_eq!(memo.len(), 0);
assert!(memo.get(Path::new("a.md")).is_none());
}
#[test]
fn a_scope_remembers_and_its_exit_forgets() {
let mut memo = ReadMemo::default();
memo.enter();
memo.remember(Path::new("a.md"), "hello", &doc("hello"));
assert_eq!(
memo.get(Path::new("a.md")).map(|(text, _)| text),
Some("hello".to_string())
);
memo.leave();
assert_eq!(memo.len(), 0);
}
#[test]
fn an_inner_scope_does_not_end_the_outer_one() {
let mut memo = ReadMemo::default();
memo.enter();
memo.remember(Path::new("a.md"), "hello", &doc("hello"));
memo.enter();
memo.leave();
assert!(
memo.get(Path::new("a.md")).is_some(),
"an inner scope's exit dropped the outer scope's memo"
);
memo.leave();
assert_eq!(memo.len(), 0);
}
#[test]
fn a_write_forgets_the_document_it_wrote() {
let mut memo = ReadMemo::default();
memo.enter();
memo.remember(Path::new("a.md"), "hello", &doc("hello"));
memo.forget(Path::new("a.md"));
assert!(memo.get(Path::new("a.md")).is_none());
assert_eq!(memo.len(), 0);
}
#[test]
fn an_oversized_document_is_not_remembered() {
let mut memo = ReadMemo::default();
memo.enter();
let huge = "x".repeat(MAX_DOCUMENT + 1);
memo.remember(Path::new("big.md"), &huge, &doc("body"));
assert_eq!(memo.len(), 0);
}
}