diff --git a/Cargo.lock b/Cargo.lock index b9e579f..8dcaa9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + [[package]] name = "clap" version = "2.33.0" @@ -683,6 +694,25 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + [[package]] name = "opaque-debug" version = "0.2.3" @@ -1197,6 +1227,7 @@ dependencies = [ "anyhow", "async-trait", "blake2", + "chrono", "digest", "env_logger", "hex", diff --git a/Cargo.toml b/Cargo.toml index 6b1060b..f393786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,4 @@ async-trait = "0.1.30" serde = "*" serde_derive = "*" horrorshow = "*" +chrono = "*" diff --git a/src/main.rs b/src/main.rs index adcaf7f..f5465ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -173,7 +173,9 @@ async fn handle_post( }; let page = write.get(post_id.to_owned()).await?; - let page = page.with_new_source_body(&get_rid_of_windows_newlines(form.get_body()?.to_owned())); + let mut page = + page.with_new_source_body(&get_rid_of_windows_newlines(form.get_body()?.to_owned())); + page.update_modification_time(); write.put(&page).await?; diff --git a/src/page.rs b/src/page.rs index a906e83..c9bb785 100644 --- a/src/page.rs +++ b/src/page.rs @@ -1,7 +1,9 @@ pub mod store; +use crate::util; #[allow(unused)] use anyhow::Result; +use chrono::prelude::*; use lazy_static::lazy_static; use std::collections::HashSet; pub use store::{InMemoryStore, Store, StoreMut}; @@ -14,11 +16,13 @@ pub type TagRef<'a> = &'a str; pub type IdRef<'a> = &'a str; const TAGWIKI_PAGE_ID_KEY: &str = "tagwiki-page-id"; +const TAGWIKI_CREATION_TIME_KEY: &str = "tagwiki-creation-time"; +const TAGWIKI_MODIFICATION_TIME_KEY: &str = "tagwiki-modification-time"; #[derive(Debug, Default, Clone)] pub struct Source(String); -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone)] pub struct Parsed { pub source: Source, pub source_body: String, @@ -50,18 +54,33 @@ fn split_headers_and_body(source: &Source) -> (&str, &str) { } } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Headers { pub id: String, - pub all: String, + pub creation_time: chrono::DateTime, + pub modification_time: chrono::DateTime, + pub other: String, } +impl Default for Headers { + fn default() -> Self { + Self { + id: util::random_string(16), + creation_time: util::now(), + modification_time: util::now(), + other: "".into(), + } + } +} impl Headers { fn parse(headers_str: &str, source: &Source) -> Headers { let mut id = None; + let mut creation = None; + let mut modification = None; + let mut other = String::new(); for line in headers_str.lines() { - match line.split(":").collect::>().as_slice() { + match line.splitn(2, ":").collect::>().as_slice() { [key, value] => { let key = key.trim(); let value = value.trim(); @@ -69,38 +88,62 @@ impl Headers { TAGWIKI_PAGE_ID_KEY => { id = Some(value.to_owned()); } - _ => {} + TAGWIKI_CREATION_TIME_KEY => { + let time = chrono::DateTime::::parse_from_rfc3339(value); + creation = time.ok(); + } + TAGWIKI_MODIFICATION_TIME_KEY => { + let time = chrono::DateTime::::parse_from_rfc3339(value); + modification = time.ok(); + } + _ => { + other.push_str(line); + other.push_str("\n") + } } } - _ => {} + _ => { + other.push_str(line); + other.push_str("\n") + } } } - match id { - Some(id) => Self { - id, - all: headers_str.to_owned(), - }, - None => { - let mut hasher = blake2::Blake2b::new(); - hasher.input(&source.0); - let res = hasher.result(); - let id = hex::encode(&res.as_slice()[0..16]); + let id = id.unwrap_or_else(|| { + let mut hasher = blake2::Blake2b::new(); + hasher.input(&source.0); + let res = hasher.result(); + hex::encode(&res.as_slice()[0..16]) + }); - let mut all = String::new(); - all.push_str(TAGWIKI_PAGE_ID_KEY); - all.push_str(": "); - all.push_str(&id); - all.push_str("\n"); - all.push_str(headers_str); + let creation: DateTime = + creation.unwrap_or_else(|| util::now()); - Self { id, all } - } + let modification = modification.unwrap_or_else(|| util::now()); + + Self { + other, + id, + creation_time: creation, + modification_time: modification, } } fn to_markdown_string(&self) -> String { - "\n" + "\n" } } @@ -186,6 +229,10 @@ impl Parsed { } } + pub fn update_modification_time(&mut self) { + self.headers.modification_time = util::now(); + } + pub fn with_new_source_body(&self, new_body_source: &str) -> Self { Self::from_headers_and_body(self.headers.clone(), new_body_source.to_owned()) } diff --git a/src/page/store/fs.rs b/src/page/store/fs.rs index deded34..99e2e45 100644 --- a/src/page/store/fs.rs +++ b/src/page/store/fs.rs @@ -108,9 +108,7 @@ impl FsStore { tokio::task::spawn_blocking(move || -> Result<()> { let mut file = std::fs::File::create(&tmp_path)?; - file.write_all(b"\n")?; + file.write_all(page.headers.to_markdown_string().as_bytes())?; file.write_all(page.source_body.as_bytes())?; file.flush()?; diff --git a/src/util.rs b/src/util.rs index 93ffa66..64abeca 100644 --- a/src/util.rs +++ b/src/util.rs @@ -7,3 +7,9 @@ pub fn random_string(len: usize) -> String { .take(len) .collect() } + +/// Now with a fixed offset of the current system timezone +pub fn now() -> chrono::DateTime { + let date = chrono::offset::Local::now(); + date.with_timezone(&date.offset()) +}