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 | |
parent | 14ad60a6d77f40fdd0b23e60e11ace8c52449c01 (diff) |
WIP: Reading pages and namespaces
-rw-r--r-- | Cargo.lock | 190 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | notes/data.md | 7 | ||||
-rw-r--r-- | src/data/content.rs | 149 | ||||
-rw-r--r-- | src/data/mod.rs | 2 | ||||
-rw-r--r-- | src/data/pages.rs | 0 |
6 files changed, 352 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock index 0689de2..2502c04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,21 @@ dependencies = [ ] [[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] name = "any_spawner" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -204,12 +219,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" [[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "shlex", +] + +[[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] name = "codee" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -303,6 +341,12 @@ dependencies = [ ] [[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -424,6 +468,16 @@ dependencies = [ ] [[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -713,6 +767,30 @@ dependencies = [ ] [[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1248,6 +1326,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60993920e071b0c9b66f14e2b32740a4e27ffc82854dcd72035887f336a09a28" [[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1793,6 +1880,12 @@ dependencies = [ ] [[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1858,7 +1951,10 @@ name = "stormscribe" version = "0.1.0" dependencies = [ "axum", + "chrono", "console_error_panic_hook", + "fs2", + "futures", "leptos", "leptos_axum", "leptos_meta", @@ -1866,7 +1962,9 @@ dependencies = [ "serde", "stylance", "tokio", + "tokio-stream", "toml", + "uuid", "wasm-bindgen", ] @@ -2067,6 +2165,17 @@ dependencies = [ ] [[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] name = "tokio-util" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2394,6 +2503,22 @@ dependencies = [ ] [[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2403,6 +2528,71 @@ dependencies = [ ] [[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + +[[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 1086f32..e53686a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,16 @@ axum = { version = "0.7", optional = true } console_error_panic_hook = { version = "0.1", optional = true} leptos_axum = { version = "0.7.0", optional = true } leptos_meta = { version = "0.7.0" } -tokio = { version = "1", features = ["rt-multi-thread", "signal"], optional = true } +tokio = { version = "1", features = ["rt-multi-thread", "signal", "fs"], optional = true } +tokio-stream = {version = "0.1.17", features = ["fs"]} wasm-bindgen = { version = "=0.2.100", optional = true } serde = { version = "^1.0.219", features = ["derive"] } stylance = "0.5.5" toml = { version = "0.8.20", features = ["parse"] } +uuid = { version = "1.16.0", features = ["v4"] } +chrono = "0.4.40" +fs2 = "0.4.3" +futures = "0.3.31" [features] default = ["ssr"] diff --git a/notes/data.md b/notes/data.md index a240bf4..c3e9874 100644 --- a/notes/data.md +++ b/notes/data.md @@ -34,5 +34,10 @@ │ ├─ slugs/[url-slug] (symlink) │ - └─ id/[uuid] + └─ id/[uuid]/ + │ + ├─ metadata.toml + │ + └─ data.bin + 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 |