diff options
Diffstat (limited to 'src/data/content.rs')
-rw-r--r-- | src/data/content.rs | 174 |
1 files changed, 141 insertions, 33 deletions
diff --git a/src/data/content.rs b/src/data/content.rs index ecd985f..a628be0 100644 --- a/src/data/content.rs +++ b/src/data/content.rs @@ -1,9 +1,12 @@ -use std::fs; +use std::fs::File; use std::collections::HashMap; +use std::io::{BufRead, BufReader}; use std::path::{PathBuf, Path}; -use std::sync::{Arc,RwLock}; +use std::sync::Arc; +use tokio::sync::RwLock; use chrono::{DateTime, Utc}; -use futures::{FutureExt, TryStreamExt}; +use leptos::prelude::StorageAccess; +use serde::Deserialize; use uuid::Uuid; use fs2::FileExt; use tokio::runtime; @@ -17,31 +20,33 @@ struct NamespaceUuid(Uuid); #[derive(Hash, PartialEq, Eq, Clone)] struct MediaUuid(Uuid); -struct ContentSnapshot { - pages: HashMap<PageUuid, Page>, - namespaces: HashMap<NamespaceUuid, Namespace>, +pub struct ContentSnapshot { + pub pages: HashMap<PageUuid, Page>, + pub namespaces: HashMap<NamespaceUuid, Namespace>, media: HashMap<MediaUuid, Media>, - namespace_path: HashMap<String, NamespaceUuid>, - page_path: HashMap<String, PageUuid>, - media_path: HashMap<String, MediaUuid>, + pub namespace_paths: HashMap<String, NamespaceUuid>, + pub page_paths: HashMap<String, PageUuid>, + media_paths: HashMap<String, MediaUuid>, - render_cache: HashMap<PageUuid, String>, + pub render_cache: HashMap<PageUuid, String>, } -struct Page { - uuid: PageUuid, - title: String, - namespace: NamespaceUuid, - slug: String, - current_version: DateTime<Utc>, - prev_version: DateTime<Utc>, +pub struct Page { + pub uuid: PageUuid, + pub namespace: NamespaceUuid, + pub author: Uuid, + pub title: String, + pub slug: String, + pub current_version: DateTime<Utc>, + pub prev_versions: Vec<DateTime<Utc>>, + content_offset: usize, } -struct Namespace { - uuid: NamespaceUuid, - path: String, - pages: Vec<PageUuid>, +pub struct Namespace { + pub uuid: NamespaceUuid, + pub path: String, + pub pages: Vec<PageUuid>, } struct Media { @@ -53,15 +58,17 @@ struct Media { used_on: Vec<PageUuid>, } -struct ContentController { - snapshot: RwLock<Box<Arc<ContentSnapshot>>>, - lock: fs::File, +#[derive(Clone)] +pub struct ContentController { + snapshot: Arc<RwLock<Box<Arc<ContentSnapshot>>>>, + lock: Arc<File>, } +#[cfg(feature = "ssr")] impl ContentController { pub fn init(data_dir: PathBuf) -> Result<Self, String> { let lock_path = Path::join(&data_dir, ".lock"); - let lockfile = fs::OpenOptions::new() + let lockfile = std::fs::OpenOptions::new() .read(true).write(true).create(true) .open(&lock_path) .map_err(|_| "Could not open data directory".to_string())?; @@ -77,19 +84,19 @@ impl ContentController { let snapshot = runtime.block_on(Self::read_data(&data_dir))?; Ok(Self { - lock: lockfile, - snapshot: RwLock::new(Box::new(Arc::new(snapshot))), + lock: Arc::new(lockfile), + snapshot: Arc::new(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 pagedata_cache = Arc::new(tokio::sync::Mutex::new(HashMap::<PageUuid, (String, NamespaceUuid)>::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 + let namespaces = 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> { @@ -122,7 +129,7 @@ impl ContentController { 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); + pagedata_cache.lock().await.insert(page_uuid.clone(), (page_slug, namespace_uuid.clone())); Some(page_uuid) }).collect::<Vec<PageUuid>>().await; @@ -134,16 +141,117 @@ impl ContentController { }) }).collect::<Vec<Namespace>>().await; + let (namespaces_by_id, namespace_paths): (HashMap<_,_>, HashMap<_,_>) = + namespaces.into_iter() + .map(|namespace| { + let namespace_uuid = namespace.uuid.clone(); + let namespace_path = namespace.path.clone(); + ( + (namespace_uuid.clone(), namespace), + (namespace_path, namespace_uuid) + ) + }) + .unzip(); + let pages_dir = Path::join(&data_dir, "pages/id"); - let page_future = fs::read_dir(&pages_dir).await + let pages = 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> { + let page_dir_path = dir_entry.as_ref().ok()?.path(); + let current_path = dir_entry.as_ref().ok()? + .metadata().await.ok()? + .is_dir() + .then_some( + fs::read_link(Path::join(&page_dir_path, "current")).await.ok() + )??; + + Page::init_from_file(¤t_path, pagedata_cache.lock().await.as_borrowed()).await + }).collect::<Vec<Page>>().await; + + let (pages_by_id, page_paths): (HashMap<_,_>, HashMap<_,_>) = + pages.into_iter() + .filter_map(|page| { + let page_uuid = page.uuid.clone(); + let namespace_path = &namespaces_by_id.get(&page.namespace)?.path; + let page_path = page.slug.clone(); - }); + Some(( + (page_uuid.clone(), page), + (format!("{namespace_path}/{page_path}"), page_uuid) + )) + }) + .unzip(); - return Err("Unimplemented".to_string()); + Ok(ContentSnapshot { + pages: pages_by_id, + namespaces: namespaces_by_id, + media: HashMap::new(), + namespace_paths, + page_paths, + media_paths: HashMap::new(), + render_cache: HashMap::new(), + }) + } + pub async fn get_snapshot(&self) -> Arc<ContentSnapshot> { + self.snapshot.read().await.as_ref().clone() } + pub async fn replace_state(&self, updated: ContentSnapshot) { + todo!() + } +} + +const METADATA_DIVIDER : &'static str = "<!-- trans rights ~ath&+ -->"; + +impl Page { + async fn init_from_file(path: &PathBuf, pagedata_cache: &HashMap::<PageUuid, (String, NamespaceUuid)>) -> Option<Self> { + let mut reader = BufReader::new(File::open(path).ok()?); + let page_uuid = PageUuid(Uuid::try_parse(&path.parent()?.file_name()?.to_str()?).ok()?); + let (page_slug, namespace_uuid) = pagedata_cache.get(&page_uuid)?.as_borrowed(); + + let mut metadata_string = String::new(); + let mut current_line = String::new(); + let mut content_offset = 0; + while let Ok(size) = reader.read_line(&mut current_line) { + content_offset += size; + if size == 0 { + return None + } + + if current_line == METADATA_DIVIDER { + break + } + + metadata_string.push_str(¤t_line); + current_line.truncate(0); + } + + #[derive(Deserialize)] + struct PageMetadata { + title: String, + author: String, + prev_versions: Option<Vec<String>>, + } + + let metadata : PageMetadata = toml::from_str(&metadata_string).ok()?; + let current_version = DateTime::parse_from_rfc3339(path.file_name()?.to_str()?.replace("_", ":").as_str()).ok()?.to_utc(); + let prev_versions = metadata.prev_versions + .unwrap_or(Vec::new()) + .iter() + .filter_map(|str| DateTime::parse_from_rfc3339(str.replace("_", ":").as_str()).ok().map(|timestamp| timestamp.to_utc())) + .collect::<Vec<_>>(); + + Some(Page { + uuid: page_uuid, + author: Uuid::try_parse(&metadata.author).ok()?, + title: metadata.title, + namespace: namespace_uuid.clone(), + slug: page_slug.clone(), + current_version, + prev_versions, + content_offset, + }) + } } |