use core::fmt;
use std::path::Path;
use rpds::RedBlackTreeMapSync;
use typst::diag::{FileError, FileResult};
use crate::{AccessModel, Bytes, ImmutPath};
#[derive(Debug, Clone)]
struct NotifyFileRepr {
mtime: crate::Time,
content: Bytes,
}
#[derive(Clone)]
pub struct FileSnapshot(Result<NotifyFileRepr, Box<FileError>>);
impl fmt::Debug for FileSnapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.as_ref() {
Ok(v) => f
.debug_struct("FileSnapshot")
.field("mtime", &v.mtime)
.field(
"content",
&FileContent {
len: v.content.len(),
},
)
.finish(),
Err(e) => f.debug_struct("FileSnapshot").field("error", &e).finish(),
}
}
}
impl FileSnapshot {
#[inline]
#[track_caller]
fn retrieve<'a, T>(&'a self, f: impl FnOnce(&'a NotifyFileRepr) -> T) -> FileResult<T> {
self.0.as_ref().map(f).map_err(|e| *e.clone())
}
pub fn mtime(&self) -> FileResult<&crate::Time> {
self.retrieve(|e| &e.mtime)
}
pub fn content(&self) -> FileResult<&Bytes> {
self.retrieve(|e| &e.content)
}
pub fn is_file(&self) -> FileResult<bool> {
self.retrieve(|_| true)
}
}
impl From<FileResult<(crate::Time, Bytes)>> for FileSnapshot {
fn from(result: FileResult<(crate::Time, Bytes)>) -> Self {
Self(
result
.map(|(mtime, content)| NotifyFileRepr { mtime, content })
.map_err(Box::new),
)
}
}
#[derive(Debug, Clone, Default)]
pub struct FileChangeSet {
pub removes: Vec<ImmutPath>,
pub inserts: Vec<(ImmutPath, FileSnapshot)>,
}
impl FileChangeSet {
pub fn is_empty(&self) -> bool {
self.inserts.is_empty() && self.removes.is_empty()
}
pub fn new_removes(removes: Vec<ImmutPath>) -> Self {
Self {
removes,
inserts: vec![],
}
}
pub fn new_inserts(inserts: Vec<(ImmutPath, FileSnapshot)>) -> Self {
Self {
removes: vec![],
inserts,
}
}
pub fn may_insert(&mut self, v: Option<(ImmutPath, FileSnapshot)>) {
if let Some(v) = v {
self.inserts.push(v);
}
}
pub fn may_extend(&mut self, v: Option<impl Iterator<Item = (ImmutPath, FileSnapshot)>>) {
if let Some(v) = v {
self.inserts.extend(v);
}
}
}
#[derive(Debug)]
pub enum MemoryEvent {
Sync(FileChangeSet),
Update(FileChangeSet),
}
#[derive(Debug)]
pub struct UpstreamUpdateEvent {
pub invalidates: Vec<ImmutPath>,
pub opaque: Box<dyn std::any::Any + Send>,
}
#[derive(Debug)]
pub enum FilesystemEvent {
Update(FileChangeSet),
UpstreamUpdate {
changeset: FileChangeSet,
upstream_event: Option<UpstreamUpdateEvent>,
},
}
#[derive(Debug)]
pub enum NotifyMessage {
Settle,
SyncDependency(Vec<ImmutPath>),
UpstreamUpdate(UpstreamUpdateEvent),
}
#[derive(Debug, Clone)]
pub struct NotifyAccessModel<M> {
files: RedBlackTreeMapSync<ImmutPath, FileSnapshot>,
pub inner: M,
}
impl<M: AccessModel> NotifyAccessModel<M> {
pub fn new(inner: M) -> Self {
Self {
files: RedBlackTreeMapSync::default(),
inner,
}
}
pub fn notify(&mut self, event: FilesystemEvent) {
match event {
FilesystemEvent::UpstreamUpdate { changeset, .. }
| FilesystemEvent::Update(changeset) => {
for path in changeset.removes {
self.files.remove_mut(&path);
}
for (path, contents) in changeset.inserts {
self.files.insert_mut(path, contents);
}
}
}
}
}
impl<M: AccessModel> AccessModel for NotifyAccessModel<M> {
fn mtime(&self, src: &Path) -> FileResult<crate::Time> {
if let Some(entry) = self.files.get(src) {
return entry.mtime().cloned();
}
self.inner.mtime(src)
}
fn is_file(&self, src: &Path) -> FileResult<bool> {
if let Some(entry) = self.files.get(src) {
return entry.is_file();
}
self.inner.is_file(src)
}
fn real_path(&self, src: &Path) -> FileResult<ImmutPath> {
if self.files.contains_key(src) {
return Ok(src.into());
}
self.inner.real_path(src)
}
fn content(&self, src: &Path) -> FileResult<Bytes> {
if let Some(entry) = self.files.get(src) {
return entry.content().cloned();
}
self.inner.content(src)
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct FileContent {
len: usize,
}