diff options
author | Ashelyn Rose <git@ashen.earth> | 2025-04-10 00:45:21 -0600 |
---|---|---|
committer | Ashelyn Rose <git@ashen.earth> | 2025-04-10 00:45:21 -0600 |
commit | e66c23f2f4fd3783364aaa15f07f8a51aaf51e3f (patch) | |
tree | 884e456dc22747e672d2b89b0b17463e1d4c8566 /src | |
parent | 14ad60a6d77f40fdd0b23e60e11ace8c52449c01 (diff) |
WIP: Reading pages and namespaces
Diffstat (limited to 'src')
-rw-r--r-- | src/data/content.rs | 149 | ||||
-rw-r--r-- | src/data/mod.rs | 2 | ||||
-rw-r--r-- | src/data/pages.rs | 0 |
3 files changed, 150 insertions, 1 deletions
diff --git a/src/data/content.rs b/src/data/content.rs new file mode 100644 index 0000000..ecd985f --- /dev/null +++ b/src/data/content.rs @@ -0,0 +1,149 @@ +use std::fs; +use std::collections::HashMap; +use std::path::{PathBuf, Path}; +use std::sync::{Arc,RwLock}; +use chrono::{DateTime, Utc}; +use futures::{FutureExt, TryStreamExt}; +use uuid::Uuid; +use fs2::FileExt; +use tokio::runtime; +use tokio_stream::wrappers::ReadDirStream; +use futures::stream::StreamExt; + +#[derive(Hash, PartialEq, Eq, Clone)] +struct PageUuid(Uuid); +#[derive(Hash, PartialEq, Eq, Clone)] +struct NamespaceUuid(Uuid); +#[derive(Hash, PartialEq, Eq, Clone)] +struct MediaUuid(Uuid); + +struct ContentSnapshot { + pages: HashMap<PageUuid, Page>, + namespaces: HashMap<NamespaceUuid, Namespace>, + media: HashMap<MediaUuid, Media>, + + namespace_path: HashMap<String, NamespaceUuid>, + page_path: HashMap<String, PageUuid>, + media_path: HashMap<String, MediaUuid>, + + render_cache: HashMap<PageUuid, String>, +} + +struct Page { + uuid: PageUuid, + title: String, + namespace: NamespaceUuid, + slug: String, + current_version: DateTime<Utc>, + prev_version: DateTime<Utc>, +} + +struct Namespace { + uuid: NamespaceUuid, + path: String, + pages: Vec<PageUuid>, +} + +struct Media { + uuid: MediaUuid, + filename: String, + mime_type: String, + uploaded_by: Uuid, + uploaded_on: Uuid, + used_on: Vec<PageUuid>, +} + +struct ContentController { + snapshot: RwLock<Box<Arc<ContentSnapshot>>>, + lock: fs::File, +} + +impl ContentController { + pub fn init(data_dir: PathBuf) -> Result<Self, String> { + let lock_path = Path::join(&data_dir, ".lock"); + let lockfile = fs::OpenOptions::new() + .read(true).write(true).create(true) + .open(&lock_path) + .map_err(|_| "Could not open data directory".to_string())?; + + lockfile.try_lock_exclusive() + .map_err(|_| "Could not lock data directory".to_string())?; + + let runtime = runtime::Builder::new_multi_thread() + .build() + .map_err(|_| "Could not start async runtime".to_string())?; + + // Read the things + let snapshot = runtime.block_on(Self::read_data(&data_dir))?; + + Ok(Self { + lock: lockfile, + snapshot: RwLock::new(Box::new(Arc::new(snapshot))), + }) + } + + async fn read_data(data_dir: &PathBuf) -> Result<ContentSnapshot, String> { + use tokio::fs; + + let page_slugs = Arc::new(tokio::sync::Mutex::new(HashMap::<PageUuid, String>::new())); + + let namespace_names_dir = Path::join(&data_dir, "namespaces/names"); + let namespace_ids_dir = Path::join(&data_dir, "namespaces/id"); + let namespace_future = fs::read_dir(&namespace_names_dir).await + .map_err(|_| "Could not open namespace directory".to_string()) + .map(|dir_entries| { ReadDirStream::new(dir_entries) })? + .filter_map(async |dir_entry| -> Option<Namespace> { + let link_path = dir_entry.as_ref().ok()?.path(); + let target_path = dir_entry.as_ref().ok()? + .metadata().await.ok()? + .is_symlink() + .then_some( + fs::read_link(link_path).await.ok() + )??; + + let last_segment = target_path.file_name()?; + target_path.parent()? + .eq(&namespace_ids_dir).then_some(())?; + + let namespace_name = dir_entry.as_ref().ok()?.file_name().to_str()?.to_string(); + let namespace_uuid = NamespaceUuid(Uuid::try_parse(last_segment.to_str()?).ok()?); + + let namespace_pages = fs::read_dir(Path::join(&namespace_ids_dir, last_segment).join("pages")).await.ok()?; + let namespace_page_uuids = ReadDirStream::new(namespace_pages) + .filter_map(async |dir_entry| -> Option<PageUuid> { + let page_path = dir_entry.as_ref().ok()?.path(); + let page_uuid = dir_entry.as_ref().ok()? + .metadata().await.ok()? + .is_symlink() + .then_some( + fs::read_link(&page_path).await.ok() + )??; + + let page_uuid = PageUuid(Uuid::try_parse(&page_uuid.to_str()?).ok()?); + let page_slug = page_path.file_name()?.to_str()?.to_string(); + + page_slugs.lock().await.insert(page_uuid.clone(), page_slug); + + Some(page_uuid) + }).collect::<Vec<PageUuid>>().await; + + Some(Namespace { + uuid: namespace_uuid, + path: namespace_name, + pages: namespace_page_uuids, + }) + }).collect::<Vec<Namespace>>().await; + + let pages_dir = Path::join(&data_dir, "pages/id"); + let page_future = fs::read_dir(&pages_dir).await + .map_err(|_| "Could not open pages data directory".to_string()) + .map(|dir_entries| { ReadDirStream::new(dir_entries) })? + .filter_map(async |dir_entry| -> Option<Page> { + + }); + + return Err("Unimplemented".to_string()); + + } + +} diff --git a/src/data/mod.rs b/src/data/mod.rs index bfe8b48..4767914 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,3 +1,3 @@ pub mod config; +pub mod content; -struct DirState diff --git a/src/data/pages.rs b/src/data/pages.rs deleted file mode 100644 index e69de29..0000000 --- a/src/data/pages.rs +++ /dev/null |