summary refs log tree commit diff
diff options
context:
space:
mode:
authorAshelyn Rose <git@ashen.earth>2025-04-10 00:45:21 -0600
committerAshelyn Rose <git@ashen.earth>2025-04-10 00:45:21 -0600
commite66c23f2f4fd3783364aaa15f07f8a51aaf51e3f (patch)
tree884e456dc22747e672d2b89b0b17463e1d4c8566
parent14ad60a6d77f40fdd0b23e60e11ace8c52449c01 (diff)
WIP: Reading pages and namespaces
-rw-r--r--Cargo.lock190
-rw-r--r--Cargo.toml7
-rw-r--r--notes/data.md7
-rw-r--r--src/data/content.rs149
-rw-r--r--src/data/mod.rs2
-rw-r--r--src/data/pages.rs0
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