use serde::{Deserialize, Serialize}; #[cfg(feature = "ssr")] use tokio::sync::Mutex; use uuid::Uuid; #[cfg(feature = "ssr")] use fs2::FileExt; #[cfg(feature = "ssr")] use std::fs::File; #[cfg(feature = "ssr")] use std::sync::LazyLock; use std::{collections::HashMap, path::Path, sync::Arc}; mod config; mod namespace; mod page; use config::Config; pub use namespace::{Namespace, Namespaces}; pub use page::{Page, Pages}; #[derive(Hash, PartialEq, Eq, Clone, Debug, Deserialize, Serialize)] pub struct PageUuid(Uuid); #[cfg(feature = "ssr")] pub static CONFIG: LazyLock = LazyLock::new(|| Config::read_from_file().expect("Could not open config file")); #[cfg(feature = "ssr")] static DATA_LOCK: LazyLock = LazyLock::new(|| { let config = &CONFIG; let lock_path = Path::join(&config.data_dir, ".lock"); let lockfile = std::fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(&lock_path) .map_err(|_| "Could not open data directory".to_string()) .unwrap(); lockfile .try_lock_exclusive() .map_err(|_| "Could not lock data directory".to_string()) .unwrap(); StormscribeData { file_lock: lockfile, data_snapshot: Mutex::new(Arc::new(DataSnapshot { namespaces: Namespaces::init(&Path::join(&config.data_dir, "namespace/")).unwrap(), pages: Pages::init(&Path::join(&config.data_dir, "pages/")).unwrap(), })), } }); #[cfg(feature = "ssr")] pub struct StormscribeData { file_lock: File, data_snapshot: Mutex>, } struct DataSnapshot { namespaces: Namespaces, pages: Pages, } #[derive(Deserialize, Serialize, Clone, Debug)] pub struct PageData { path: String, metadata: Option, content: String, } #[cfg(feature = "ssr")] impl StormscribeData { async fn get_snapshot() -> Arc { DATA_LOCK.data_snapshot.lock().await.clone() } pub async fn get_namespace() -> Namespace { StormscribeData::get_snapshot() .await .namespaces .root .clone() } pub async fn get_all_pages() -> HashMap { StormscribeData::get_snapshot().await.pages.pages.clone() } pub async fn get_page_data(page_path: String) -> PageData { let data = Self::get_snapshot().await; let page = data .namespaces .get_page_uuid(&page_path) .map(|page_uuid| data.pages.get_page(&page_uuid)) .flatten(); let content = if let Some(page) = page.cloned() { page.read_content().await.ok() } else { None }; PageData { path: page_path, metadata: page.cloned(), content: content.unwrap_or(String::new()), } } }