use std::sync::Arc;
use crate::crdt::{BodyDocManager, CrdtStorage, MemoryStorage, WorkspaceCrdt};
use crate::fs::AsyncFileSystem;
use super::callback_registry::CallbackRegistry;
use super::crdt_fs::CrdtFs;
use super::event_fs::EventEmittingFs;
pub struct DecoratedFs<FS: AsyncFileSystem> {
pub fs: EventEmittingFs<CrdtFs<FS>>,
pub workspace_crdt: Arc<WorkspaceCrdt>,
pub body_doc_manager: Arc<BodyDocManager>,
pub event_registry: Arc<CallbackRegistry>,
pub storage: Arc<dyn CrdtStorage>,
}
impl<FS: AsyncFileSystem> DecoratedFs<FS> {
pub fn set_crdt_enabled(&self, enabled: bool) {
self.fs.inner().set_enabled(enabled);
}
pub fn is_crdt_enabled(&self) -> bool {
self.fs.inner().is_enabled()
}
pub fn set_events_enabled(&self, enabled: bool) {
self.fs.set_enabled(enabled);
}
pub fn is_events_enabled(&self) -> bool {
self.fs.is_enabled()
}
pub fn on_event(
&self,
callback: super::callback_registry::EventCallback,
) -> super::callback_registry::SubscriptionId {
self.fs.on_event(callback)
}
pub fn off_event(&self, id: super::callback_registry::SubscriptionId) -> bool {
self.fs.off_event(id)
}
pub fn base_fs(&self) -> &FS {
self.fs.inner().inner()
}
pub fn crdt_fs(&self) -> &CrdtFs<FS> {
self.fs.inner()
}
pub fn event_fs(&self) -> &EventEmittingFs<CrdtFs<FS>> {
&self.fs
}
}
impl<FS: AsyncFileSystem + Clone> Clone for DecoratedFs<FS> {
fn clone(&self) -> Self {
Self {
fs: self.fs.clone(),
workspace_crdt: Arc::clone(&self.workspace_crdt),
body_doc_manager: Arc::clone(&self.body_doc_manager),
event_registry: Arc::clone(&self.event_registry),
storage: Arc::clone(&self.storage),
}
}
}
impl<FS: AsyncFileSystem> std::fmt::Debug for DecoratedFs<FS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DecoratedFs")
.field("crdt_enabled", &self.is_crdt_enabled())
.field("events_enabled", &self.is_events_enabled())
.field("workspace_crdt", &self.workspace_crdt)
.field("body_doc_manager", &self.body_doc_manager)
.finish()
}
}
pub struct DecoratedFsBuilder<FS: AsyncFileSystem> {
base: FS,
storage: Option<Arc<dyn CrdtStorage>>,
crdt_enabled: bool,
events_enabled: bool,
}
impl<FS: AsyncFileSystem> DecoratedFsBuilder<FS> {
pub fn new(base: FS) -> Self {
Self {
base,
storage: None,
crdt_enabled: true,
events_enabled: true,
}
}
pub fn with_crdt(mut self, storage: Arc<dyn CrdtStorage>) -> Self {
self.storage = Some(storage);
self
}
pub fn crdt_enabled(mut self, enabled: bool) -> Self {
self.crdt_enabled = enabled;
self
}
pub fn events_enabled(mut self, enabled: bool) -> Self {
self.events_enabled = enabled;
self
}
pub fn build(self) -> DecoratedFs<FS> {
let storage: Arc<dyn CrdtStorage> = self
.storage
.unwrap_or_else(|| Arc::new(MemoryStorage::new()));
let workspace_crdt = Arc::new(WorkspaceCrdt::new(Arc::clone(&storage)));
let body_doc_manager = Arc::new(BodyDocManager::new(Arc::clone(&storage)));
let crdt_fs = CrdtFs::new(
self.base,
Arc::clone(&workspace_crdt),
Arc::clone(&body_doc_manager),
);
crdt_fs.set_enabled(self.crdt_enabled);
let event_registry = Arc::new(CallbackRegistry::new());
let event_fs = EventEmittingFs::with_registry(crdt_fs, Arc::clone(&event_registry));
event_fs.set_enabled(self.events_enabled);
DecoratedFs {
fs: event_fs,
workspace_crdt,
body_doc_manager,
event_registry,
storage,
}
}
pub fn build_with_load(self) -> crate::error::Result<DecoratedFs<FS>> {
let storage: Arc<dyn CrdtStorage> = self
.storage
.unwrap_or_else(|| Arc::new(MemoryStorage::new()));
let workspace_crdt = Arc::new(WorkspaceCrdt::load(Arc::clone(&storage))?);
let body_doc_manager = Arc::new(BodyDocManager::new(Arc::clone(&storage)));
let crdt_fs = CrdtFs::new(
self.base,
Arc::clone(&workspace_crdt),
Arc::clone(&body_doc_manager),
);
crdt_fs.set_enabled(self.crdt_enabled);
let event_registry = Arc::new(CallbackRegistry::new());
let event_fs = EventEmittingFs::with_registry(crdt_fs, Arc::clone(&event_registry));
event_fs.set_enabled(self.events_enabled);
Ok(DecoratedFs {
fs: event_fs,
workspace_crdt,
body_doc_manager,
event_registry,
storage,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::{InMemoryFileSystem, SyncToAsyncFs};
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
fn create_test_base_fs() -> SyncToAsyncFs<InMemoryFileSystem> {
SyncToAsyncFs::new(InMemoryFileSystem::new())
}
#[test]
fn test_build_with_default_storage() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).build();
assert!(decorated.is_crdt_enabled());
assert!(decorated.is_events_enabled());
}
#[test]
fn test_build_with_custom_storage() {
let base = create_test_base_fs();
let storage: Arc<dyn CrdtStorage> = Arc::new(MemoryStorage::new());
let decorated = DecoratedFsBuilder::new(base).with_crdt(storage).build();
assert!(decorated.is_crdt_enabled());
}
#[test]
fn test_build_with_disabled_crdt() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).crdt_enabled(false).build();
assert!(!decorated.is_crdt_enabled());
}
#[test]
fn test_build_with_disabled_events() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).events_enabled(false).build();
assert!(!decorated.is_events_enabled());
}
#[test]
fn test_runtime_toggle_crdt() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).build();
assert!(decorated.is_crdt_enabled());
decorated.set_crdt_enabled(false);
assert!(!decorated.is_crdt_enabled());
decorated.set_crdt_enabled(true);
assert!(decorated.is_crdt_enabled());
}
#[test]
fn test_runtime_toggle_events() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).build();
assert!(decorated.is_events_enabled());
decorated.set_events_enabled(false);
assert!(!decorated.is_events_enabled());
decorated.set_events_enabled(true);
assert!(decorated.is_events_enabled());
}
#[test]
fn test_event_subscription() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).build();
let event_count = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&event_count);
let id = decorated.on_event(Arc::new(move |_| {
counter.fetch_add(1, Ordering::SeqCst);
}));
futures_lite::future::block_on(async {
decorated
.fs
.write_file(Path::new("test.md"), "content")
.await
.unwrap();
});
assert_eq!(event_count.load(Ordering::SeqCst), 1);
assert!(decorated.off_event(id));
futures_lite::future::block_on(async {
decorated
.fs
.write_file(Path::new("test2.md"), "content")
.await
.unwrap();
});
assert_eq!(event_count.load(Ordering::SeqCst), 1);
}
#[test]
fn test_crdt_updates_on_write() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).build();
futures_lite::future::block_on(async {
decorated
.fs
.write_file(
Path::new("test.md"),
"---\ntitle: Test File\n---\nBody content",
)
.await
.unwrap();
});
let metadata = decorated.workspace_crdt.get_file("test.md").unwrap();
assert_eq!(metadata.title, Some("Test File".to_string()));
}
#[test]
fn test_crdt_disabled_skips_updates() {
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base).crdt_enabled(false).build();
futures_lite::future::block_on(async {
decorated
.fs
.write_file(Path::new("test.md"), "---\ntitle: Test\n---\nBody")
.await
.unwrap();
});
assert!(decorated.workspace_crdt.get_file("test.md").is_none());
}
#[test]
fn test_build_with_load() {
let storage: Arc<dyn CrdtStorage> = Arc::new(MemoryStorage::new());
{
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base)
.with_crdt(Arc::clone(&storage))
.build();
futures_lite::future::block_on(async {
decorated
.fs
.write_file(Path::new("test.md"), "---\ntitle: Persistent\n---\nBody")
.await
.unwrap();
});
decorated.workspace_crdt.save().unwrap();
}
{
let base = create_test_base_fs();
let decorated = DecoratedFsBuilder::new(base)
.with_crdt(Arc::clone(&storage))
.build_with_load()
.unwrap();
let metadata = decorated.workspace_crdt.get_file("test.md").unwrap();
assert_eq!(metadata.title, Some("Persistent".to_string()));
}
}
}