diff --git a/src/defaults.rs b/src/defaults.rs index 06dbade..1b0552e 100644 --- a/src/defaults.rs +++ b/src/defaults.rs @@ -1,4 +1,5 @@ use crate::mnemonic::MnemonicSaltKind; +use crate::{version, version::Version}; /// Default anonymous name pub const ANON_NAME: &'static str = "ๅ็„กใ—"; @@ -55,7 +56,7 @@ static_assert!(STATE_STREAM_BUFFER_SIZE > 0); /// The current version of the file formats for saving state /// /// TODO: Create `Version` type that takes from `env!(version)` at compile time. -pub const VERSION: u32 = 0; +pub const VERSION: Version = version!(0); /// Max size of a HTTP request body to process. pub const MAX_CONTENT_LENGTH: u64 = (1024 * 1024) * 15; //15MB diff --git a/src/main.rs b/src/main.rs index c5e98bc..1a7ef23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,7 @@ static GLOBAL: Jemalloc = Jemalloc; #[macro_use] mod ext; use ext::*; +mod version; mod service; mod bytes; mod delta; //unused now, but tests still use it, so... diff --git a/src/state/cache.rs b/src/state/cache.rs new file mode 100644 index 0000000..8f7a476 --- /dev/null +++ b/src/state/cache.rs @@ -0,0 +1,65 @@ +use super::*; +use std::collections::HashMap; +use tokio::time::{ + DelayQueue, + delay_queue, +}; + + +#[derive(Debug)] +pub struct Cache +{ + //TODO: HashMap and DelayQueue of mapping public keys to user IDs, etc. + pkey_maps: HashMap, + pkey_rems: DelayQueue, +} + +impl Cache +{ + /// Create a new empty cache + pub fn new() -> Self + { + Self { + pkey_maps: HashMap::new(), + pkey_rems: DelayQueue::new(), + } + } + + //how tf do we get this to run concurrently with insertions??? it holds `&mut self` forever! + //redesign required. maybe using Arc and RwLock or Mutex and interrupting the purge task when something needs to be inserted. i.e: give this task a stream that it can `select!` along with calling `next()`, if the other future completes first, we return? but wouldn't that lose us an item in `next()`? is there a `select with priority` in `futures`? i think there is. eh... + /// Run a purge on this cache. + pub async fn purge(&mut self) -> eyre::Result<()> + { + let mut errors = Vec::default(); + while let Some(entry) = self.pkey_rems.next().await + { + match entry { + Ok(entry) => { + self.pkey_maps.remove(entry.get_ref()); + }, + Err(err) => { + errors.push(err); + } + } + } + if errors.len() > 0 { + let mut err = Err(eyre!("One or more removals failed")) + .with_note(|| errors.len().to_string().header("Number of failed removals")); + for e in errors.into_iter() + { + err = err.with_error(move || e); + } + return err; + } else { + Ok(()) + } + } + /// Purge all values available to be purged now. + pub fn purge_now(&mut self) -> eyre::Result<()> + { + match self.purge().now_or_never() { + Some(x) => x, + None => Ok(()) + } + } +} diff --git a/src/state/freeze.rs b/src/state/freeze.rs index a02ddcb..d568f3f 100644 --- a/src/state/freeze.rs +++ b/src/state/freeze.rs @@ -40,7 +40,7 @@ impl FreezeMetadata { Self { chk: *FREEZE_CHK, - version: defaults::VERSION, + version: defaults::VERSION.to_u32(), body_size: from.len().try_into().unwrap(), //this should never fail, right? compressed: false, body_hash: sha256::compute_slice(from), @@ -307,7 +307,8 @@ impl Freeze posts_map, users_map, posts_user_map - }) + }), + cache: Cache::new(), })) } @@ -336,6 +337,8 @@ impl Freeze } State(Arc::new(Inner { + cache: Cache::new(), + oneesan: RwLock::new(Oneesan { users, posts, diff --git a/src/state/mod.rs b/src/state/mod.rs index f33bed6..879bbf5 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -13,6 +13,9 @@ use futures::prelude::*; use user::{User, UserID}; use post::{Post, PostID}; +mod cache; +pub use cache::*; + mod freeze; pub use freeze::*; @@ -44,6 +47,9 @@ struct Inner { /// The posts and user state. oneesan: RwLock, + + /// A non-persistant cache + cache: Cache, } /// Contains all posts and users @@ -65,7 +71,8 @@ impl State posts_map: HashMap::new(), posts_user_map: HashMap::new(), - }) + }), + cache: Cache::new(), } )) } diff --git a/src/template/mod.rs b/src/template/mod.rs index 403624c..628d100 100644 --- a/src/template/mod.rs +++ b/src/template/mod.rs @@ -14,7 +14,7 @@ mod view pub fn head() -> Markup { html! { - //TODO + //TODO } } @@ -44,5 +44,6 @@ mod view /// Post view page pub fn view(state: &state::State) -> Markup { + //TODO: Create one-time-use token system for rendering page. Insert into state's one-time-use tokens page(view::head(), view::body(state)) } diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..44164a6 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,207 @@ +use std::fmt; +use std::cmp::Ordering; + +/// Represents a semver version number of the order `major.minor.bugfix`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] //TODO: Make these impls instead of derives, because safe packed borrows. +#[repr(C, packed)] +pub struct Version(u8,u8,u16); + +impl fmt::Display for Version +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}.{}.{}", self.major(), self.minor(), self.bugfix()) + } +} + +impl From for Version +{ + #[inline] fn from(from: u32) -> Self + { + Self::from_u32(from) + } +} + +impl From for u32 +{ + fn from(from: Version) -> Self + { + from.to_u32() + } +} + +impl PartialEq for u32 +{ + #[inline] fn eq(&self, other: &Version) -> bool + { + *self == other.to_u32() + } +} +impl PartialEq for Version +{ + #[inline] fn eq(&self, other: &u32) -> bool + { + self.to_u32() == *other + } +} +impl PartialOrd for Version +{ + #[inline] fn partial_cmp(&self, other: &u32) -> Option { + self.to_u32().partial_cmp(other) + } +} +impl PartialOrd for u32 +{ + #[inline] fn partial_cmp(&self, other: &Version) -> Option { + self.partial_cmp(&other.to_u32()) + } +} + +impl From<()> for Version +{ + #[inline] fn from(from: ()) -> Self + { + Self(0,0,0) + } +} + + +impl From<(usize,)> for Version +{ + #[inline] fn from(from: (usize,)) -> Self + { + Self::new(from.0,0,0) + } +} + + +impl From<(usize, usize)> for Version +{ + #[inline] fn from((ma, mi): (usize, usize)) -> Self + { + Self::new(ma, mi, 0) + } +} + + +impl From<(usize, usize, usize)> for Version +{ + #[inline] fn from((ma,mi,bu): (usize, usize, usize)) -> Self + { + Self::new(ma,mi,bu) + } +} + +impl From<(u8, u8, u16)> for Version +{ + #[inline] fn from((ma, mi, bu): (u8, u8, u16)) -> Self + { + Self(ma,mi,bu) + } +} + +impl From for (u8, u8, u16) +{ + #[inline] fn from(from: Version) -> Self + { + (from.0, from.1, from.2) + } +} + +impl From for (usize, usize, usize) +{ + #[inline] fn from(from: Version) -> Self + { + (from.major(), from.minor(), from.bugfix()) + } +} + +impl Version +{ + /// The major component of this `Version`. + #[inline] pub const fn major(self) -> usize + { + self.0 as usize + } + /// The minor component of this `Version`. + #[inline] pub const fn minor(self) -> usize + { + self.1 as usize + } + /// The bugfix component of this `Version`. + #[inline] pub const fn bugfix(self) -> usize + { + self.2 as usize + } + + /// Convert to a 32 bit integer representation + #[inline] pub const fn to_u32(self) -> u32 + { + let mb = self.2.to_be_bytes(); + u32::from_be_bytes([ + self.0, + self.1, + mb[0], + mb[1], + ]) + } + /// Convert to a 32 bit integer representation + #[inline] pub const fn from_u32(from: u32) -> Self + { + let bytes = from.to_be_bytes(); + Self( + bytes[0], + bytes[1], + u16::from_be_bytes([bytes[2], bytes[3]]), + ) + } + + /// Create a new version object + #[inline] pub const fn new_exact(major: u8, minor: u8, bugfix: u16) -> Self + { + Self(major,minor,bugfix) + } + + /// Create a new version object + /// + /// # Panics + /// If any of the components do not fit within their bounds. + #[inline] pub fn new(major: usize, minor: usize, bugfix: usize) -> Self + { + use std::convert::TryInto; + Self::new_exact(major.try_into().expect("Major exceeded limit of u8"), + minor.try_into().expect("Minor exceeded limit of u8"), + bugfix.try_into().expect("Bugfix exceeded limit of u16"), + ) + } +} + +#[macro_export] macro_rules! version { + ($maj:expr, $min:expr, $bfx:expr) => ($crate::version::Version::new_exact($maj as u8, $min as u8, $bfx as u16)); + ($maj:expr, $min:expr) => ($crate::version::Version::new_exact($maj as u8, $min as u8, 0)); + ($maj:expr) => ($crate::version::Version::new_exact($maj as u8, 0, 0)); +} + + +#[cfg(test)] +mod tests +{ + use super::*; + #[test] + fn ordinal() + { + assert!( version!(1) > version!(0, 9, 1)); + assert!( version!(2) > version!(1, 9, 300)); + assert!( (version!(1)..version!(1, 9)).contains(&version!(1, 8))); + assert!( !(version!(1)..version!(2)).contains(&version!(2, 10, 432))); + + println!("{}: {}", version!(1), version!(1).to_u32()); + println!("{}: {}", version!(0,9,1), version!(0,9,1).to_u32()); + + assert!( version!(1).to_u32() > version!(0, 9, 1).to_u32()); + assert!( version!(2).to_u32() > version!(1, 9, 300).to_u32()); + assert!( (version!(1).to_u32()..version!(1, 9).to_u32()).contains(&version!(1, 8).to_u32())); + assert!( !(version!(1).to_u32()..version!(2).to_u32()).contains(&version!(2, 10, 432).to_u32())); + + } +} diff --git a/src/web/route.rs b/src/web/route.rs index c1b4f1e..370ff54 100644 --- a/src/web/route.rs +++ b/src/web/route.rs @@ -50,6 +50,14 @@ pub fn setup(state: State) -> impl warp::Filter //TODO: What output should this get_post_by_id .or(get_posts_by_user_id) }); - root.and(post_api. - or(get_api)) + let render_api = warp::get() + .and(state.clone()) + .and_then(|state: State| { + async move { + Ok::<_, std::convert::Infallible>(template::view(&state).into_string()) + } + }); + root.and(post_api + .or(get_api) + .or(render_api)) }