about summary refs log tree commit diff
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore1
-rwxr-xr-xCargo.lock30
-rwxr-xr-xCargo.toml3
-rwxr-xr-xdev.conf3
-rw-r--r--src/db.rs64
-rwxr-xr-xsrc/main.rs111
6 files changed, 197 insertions, 15 deletions
diff --git a/.gitignore b/.gitignore
index ea8c4bf..2ea0d11 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /target
+database.sqlite
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 861ad2c..a34a6f4 100755
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -54,6 +54,7 @@ dependencies = [
  "cutie",
  "rand",
  "rusqlite",
+ "serde",
  "sha2",
  "snafu",
  "time",
@@ -173,6 +174,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
 
 [[package]]
+name = "cc"
+version = "1.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
 name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -472,6 +482,7 @@ version = "0.31.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ad8935b44e7c13394a179a438e0cebba0fe08fe01b54f152e29a93b5cf993fd4"
 dependencies = [
+ "cc",
  "pkg-config",
  "vcpkg",
 ]
@@ -781,6 +792,7 @@ dependencies = [
  "hashlink",
  "libsqlite3-sys",
  "smallvec",
+ "time",
 ]
 
 [[package]]
@@ -809,18 +821,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
 name = "serde"
-version = "1.0.210"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.210"
+version = "1.0.217"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -882,6 +894,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"
@@ -929,9 +947,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.77"
+version = "2.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
+checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 41dba62..173a739 100755
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,8 @@ time = { version = "0.3.36", features = [
 ] }
 sha2 = "0.10.8"
 url = "2.5.0"
-rusqlite = "0.33.0"
+rusqlite = { version = "0.33.0", features = ["bundled", "time"] }
+serde = { version = "1.0.217", features = ["derive"] }
 
 # for generating Atom URNs
 [dependencies.rand]
diff --git a/dev.conf b/dev.conf
index 44275bc..2e1e952 100755
--- a/dev.conf
+++ b/dev.conf
@@ -1,3 +1,4 @@
-Webroot /Users/gen/src/inf/served
+Webroot /home/gen/src/inf/served
 Templates ../templates
 Hostname dreamy.place
+Database database.sqlite
diff --git a/src/db.rs b/src/db.rs
new file mode 100644
index 0000000..d4038cb
--- /dev/null
+++ b/src/db.rs
@@ -0,0 +1,64 @@
+use std::sync::Mutex;
+
+use camino::Utf8PathBuf;
+use rusqlite::{params, Connection, OptionalExtension};
+use time::OffsetDateTime;
+
+use crate::Meminfo;
+
+
+pub struct Database{ 
+    db_path: Utf8PathBuf,
+    conn: Mutex<Connection>
+}
+
+impl Database {
+    pub fn new(db_path: Utf8PathBuf) -> Self {
+        Self {
+            conn:Mutex::new(Connection::open(&db_path).unwrap()),
+            db_path,
+        }
+    }
+
+    pub fn create_tables(&self) {
+        let conn = self.conn.lock().unwrap();
+        conn.execute(CREATE_TABLE_HOSTMEM, params![]).unwrap();
+    }
+
+    pub fn insert_host_meminfo(&self, meminfo: Meminfo) {
+        let conn = self.conn.lock().unwrap();
+
+        conn.execute("INSERT INTO stats_hostmem(total_kb, available_kb) VALUES (?1, ?2)", params![meminfo.total, meminfo.avaialable]).unwrap();
+    }
+
+    pub fn get_last_host_meminfo(&self) -> DbMeminfo {
+        let conn = self.conn.lock().unwrap();
+
+        conn.query_row("SELECT * FROM stats_hostmem ORDER BY stamp DESC LIMIT 1", [], |row| {
+            let (stamp, total_kb, available_kb) = row.try_into().unwrap();
+
+            Ok(DbMeminfo {
+                stamp, total_kb, available_kb
+            })
+        }).optional().unwrap().unwrap()
+    }
+}
+
+pub const CREATE_TABLE_HOSTMEM: &'static str = "\
+    CREATE TABLE IF NOT EXISTS stats_hostmem (
+        stamp TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
+        total_kb INTEGER NOT NULL,
+        available_kb INTEGER NOT NULL
+    );";
+
+pub struct DbMeminfo {
+    pub stamp: OffsetDateTime,
+    pub total_kb: usize,
+    pub available_kb: usize
+}
+
+impl DbMeminfo {
+    pub fn usage(&self) -> usize {
+        self.total_kb - self.available_kb
+    }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index fa1ac5c..26e1cf0 100755
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,12 +7,13 @@ mod settings;
 mod templated;
 mod timeparse;
 mod util;
+mod db;
 
-use std::{io::Write, os::unix::fs::MetadataExt, str::FromStr};
+use std::{fs::File, io::{BufRead, BufReader, Write}, os::unix::fs::MetadataExt, str::FromStr, sync::Arc, time::Duration};
 
 use axum::{
 	body::Body,
-	extract::Path,
+	extract::{Path, State},
 	http::{header, StatusCode},
 	response::Response,
 	routing::get,
@@ -21,9 +22,11 @@ use axum::{
 use bempline::{variables, Document, Options};
 use camino::Utf8PathBuf;
 use confindent::{Confindent, Node};
+use db::Database;
 pub use error::RuntimeError;
 use fs::Filesystem;
 use settings::Settings;
+use templated::Frontmatter;
 use tokio_util::io::ReaderStream;
 use tracing_subscriber::{fmt::time, prelude::*, EnvFilter};
 use util::{Referer, RemoteIp, SessionId};
@@ -33,6 +36,11 @@ use crate::{
 	templated::Templated,
 };
 
+#[derive(Clone)]
+pub struct AwakeState {
+	pub database: Arc<Database>
+}
+
 #[tokio::main]
 async fn main() {
 	match std::env::args().nth(1).as_deref() {
@@ -55,9 +63,27 @@ async fn main() {
 	let webroot: Utf8PathBuf = conf.child_parse("Webroot").unwrap();
 	let templates = conf.child_value("Templates").unwrap();
 	let hostname = conf.child_owned("Hostname").unwrap();
+	let dbpath = conf.child_owned("Database").unwrap();
+
+	let database = Database::new(dbpath.into());
+	database.create_tables();
 
 	let fs = Filesystem::new(&webroot);
 
+	let state = AwakeState {
+		database: Arc::new(database)
+	};
+
+	let mi_state = state.clone();
+	let meminfo_thread = std::thread::spawn(move || {
+		loop {
+			let meminfo = Meminfo::current();
+			mi_state.database.insert_host_meminfo(meminfo);
+
+			std::thread::sleep(Duration::from_secs(60));
+		}
+	});
+
 	let settings = Settings {
 		template_dir: Utf8PathBuf::from(webroot.join(templates))
 			.canonicalize_utf8()
@@ -69,29 +95,31 @@ async fn main() {
 		.route("/", get(index_handler))
 		.route("/*path", get(handler))
 		.layer(Extension(fs))
-		.layer(Extension(settings));
+		.layer(Extension(settings)).with_state(state);
 
 	let listener = tokio::net::TcpListener::bind("0.0.0.0:2560").await.unwrap();
 	axum::serve(listener, app).await.unwrap()
 }
 
 async fn index_handler(
+	state: State<AwakeState>,
 	fse: Extension<Filesystem>,
 	se: Extension<Settings>,
 	sid: SessionId,
 	rfr: Option<Referer>,
 ) -> Response {
-	handler(fse, se, sid, rfr, Path(String::from("/"))).await
+	handler(state, fse, se, sid, rfr, Path(String::from("/"))).await
 }
 
 async fn handler(
+	State(state): State<AwakeState>,
 	Extension(fs): Extension<Filesystem>,
 	Extension(settings): Extension<Settings>,
 	sid: SessionId,
 	rfr: Option<Referer>,
 	Path(path): Path<String>,
 ) -> Response {
-	match falible_handler(fs, settings, sid, rfr, path).await {
+	match falible_handler(state, fs, settings, sid, rfr, path).await {
 		Ok(resp) => resp,
 		Err(re) => Response::builder()
 			.body(Body::from(re.to_string()))
@@ -100,6 +128,7 @@ async fn handler(
 }
 
 async fn falible_handler(
+	state: AwakeState,
 	fs: Filesystem,
 	settings: Settings,
 	sid: SessionId,
@@ -134,7 +163,7 @@ async fn falible_handler(
 		let result = Templated::from_str(&content);
 
 		match result {
-			Ok(templated) => send_template(templated, resolve, webpath, settings, sid).await,
+			Ok(templated) => send_template(templated, resolve, webpath, state, settings, sid).await,
 			Err(e) => {
 				tracing::warn!("error sending template {e}");
 
@@ -203,6 +232,7 @@ async fn send_template(
 	templated: Templated,
 	resolve: PathResolution,
 	webpath: Webpath,
+	state: AwakeState,
 	settings: Settings,
 	sid: SessionId,
 ) -> Result<Response, RuntimeError> {
@@ -335,7 +365,11 @@ async fn send_template(
 	tracing::trace!("finished published block");
 
 	// insert the page content itself
-	let markedup = markup::process(&templated.content);
+	let mut markedup = markup::process(&templated.content);
+	if templated.frontmatter.get("use-template").is_some() {
+		markedup = template_content(state, &templated.frontmatter, markedup);
+	}
+
 	template.set("main", markedup);
 
 	Ok(Response::builder()
@@ -343,3 +377,66 @@ async fn send_template(
 		.body(Body::from(template.compile()))
 		.unwrap())
 }
+
+fn template_content(state: AwakeState, frontmatter: &Frontmatter, marked: String) -> String {
+	let Ok(mut doc) = Document::from_str(&marked, Options::default()) else {
+		return marked
+	};
+
+	if frontmatter.get("system-stats").is_some() {
+		let mem = state.database.get_last_host_meminfo();
+
+		doc.set("stats.mem.total", mem.total_kb / 1024);
+		doc.set("stats.mem.usage", mem.usage() / 1024);
+	}
+
+	doc.compile()
+}
+
+struct Meminfo {
+	pub total: usize,
+	pub free: usize,
+	pub avaialable: usize
+}
+
+impl Meminfo {
+	pub fn current() -> Self {
+		let procinfo = File::open("/proc/meminfo").unwrap();
+		let bread = BufReader::new(procinfo);
+
+		let mut meminfo = Meminfo {
+			total: 0,
+			free: 0,
+			avaialable: 0
+		};
+
+		for line in bread.lines() {
+			let line = line.unwrap();
+
+			if let Some((raw_key, raw_value_kb)) = line.split_once(':') {
+				let value = if let Some(raw_value) = raw_value_kb.trim().strip_suffix(" kB") {
+					if let Ok(parsed) = raw_value.parse() {
+						parsed
+					} else {
+						continue;
+					}
+				} else {
+					continue;
+				};
+
+				match raw_key.trim() {
+					"MemTotal" => meminfo.total = value,
+					"MemFree" => meminfo.free = value,
+					"MemAvailable" => meminfo.avaialable = value,
+					_ => ()
+				}
+			}
+		}
+
+		meminfo
+	}
+
+	pub fn usage(&self) -> usize {
+		self.total - self.avaialable
+	}
+}
\ No newline at end of file