crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

commit 764a63707126dc0ab3d2833fadb0313de21ea75a
parent 5f1efb1eab8b086e3339633d7242e2c93bd60ccc
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Wed, 22 Dec 2021 14:41:19 -0800

a bunch of wip stuf

Diffstat:
Msrc/config.rs | 9+++++++++
Msrc/main.rs | 3++-
Asrc/time.rs | 143+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/utils.rs | 34----------------------------------
4 files changed, 154 insertions(+), 35 deletions(-)

diff --git a/src/config.rs b/src/config.rs @@ -11,6 +11,10 @@ pub struct Config { pub list_email: String, pub url: String, pub homepage: String, + // mimes that will be preserved as raw attachment files + // wildcards allowed as * + // WIP + pub ok_attachments: Vec<String>, pub out_dir: PathBuf, } @@ -26,6 +30,7 @@ impl Config { let mut list_name = "Crabmail Mailing List".to_string(); let mut list_email = "setme@foo.local".to_string(); let mut url = "flounder.online".to_string(); + let mut ok_attachments = vec!["text/python".to_string()]; let mut homepage = String::new(); for l in io::BufReader::new(file).lines() { @@ -41,6 +46,9 @@ impl Config { "list_email" => list_email = value.to_string(), "url" => url = value.to_string(), "homepage" => homepage = value.to_string(), + "ok_attachments" => { + ok_attachments = value.split(",").map(|s| s.to_owned()).collect() + } _ => {} } } else { @@ -54,6 +62,7 @@ impl Config { url, homepage, out_dir: PathBuf::from(""), + ok_attachments, }) } } diff --git a/src/main.rs b/src/main.rs @@ -23,6 +23,7 @@ use urlencoding; use config::{Config, INSTANCE}; mod config; mod mbox; +mod time; mod utils; // TODO be more clear about the expected input types @@ -171,7 +172,7 @@ impl<'a> ThreadList<'a> { } span(class="timeago") { - : format!(" | {} replies | {}", thread.messages.len() - 1, utils::timeago(thread.last_reply())) + : format!(" | {} replies | {}", thread.messages.len() - 1, time::timeago(thread.last_reply())) } } } diff --git a/src/time.rs b/src/time.rs @@ -0,0 +1,143 @@ +/// Useful "Good enough" time utils in Rust +use std::time::{SystemTime, UNIX_EPOCH}; + +const DAYS_IN_MONTHS: [i64; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; + +/* 2000-03-01 (mod 400 year, immediately after feb29 */ +const LEAP_EPOCH: i64 = 946684800 + 86400 * (31 + 29); + +const DAYS_PER_400Y: i64 = (365 * 400 + 97); +const DAYS_PER_100Y: i64 = (365 * 100 + 24); +const DAYS_PER_4Y: i64 = (365 * 4 + 1); + +// TODO fiture out types +#[derive(Debug, Clone)] +pub struct Date { + year: u32, + month: u32, + day_of_month: u32, + day_of_week: u32, + hour: u32, + minute: u32, + second: u32, +} + +// from http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c +// with a slightly different API +// this is a line-for-line copy, not idiomatic rust +// TODO handle 64-bit overflow +pub fn secs_to_date(unixtime: u64) -> Date { + let secs = unixtime as i64 - LEAP_EPOCH; + let mut days = secs / 86400; + let mut remsecs = secs % 86400; + if (remsecs < 0) { + remsecs += 86400; + days -= 1; + } + let mut wday = (3 + days) % 7; + if (wday < 0) { + wday += 7 + }; + let mut qc_cycles = days / DAYS_PER_400Y; + let mut remdays = days % DAYS_PER_400Y; + if (remdays < 0) { + remdays += DAYS_PER_400Y; + qc_cycles -= 1; + } + let mut c_cycles = remdays / DAYS_PER_100Y; + if (c_cycles == 4) { + c_cycles -= 1; + } + remdays -= c_cycles * DAYS_PER_100Y; + + let mut q_cycles = remdays / DAYS_PER_4Y; + if (q_cycles == 25) { + q_cycles -= 1 + } + remdays -= q_cycles * DAYS_PER_4Y; + + let mut remyears = remdays / 365; + if (remyears == 4) { + remyears -= 1 + } + remdays -= remyears * 365; + + // C + let leap = match remyears == 0 && (q_cycles != 0 || c_cycles == 0) { + true => 1, + false => 0, + }; + let mut yday = remdays + 31 + 28 + leap; + if (yday >= 365 + leap) { + yday -= 365 + leap + } + + let mut years = remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; + let mut months: i64 = 0; + while DAYS_IN_MONTHS[months as usize] <= remdays { + remdays -= DAYS_IN_MONTHS[months as usize]; + months += 1; + } + // some sort of weird off by 1 error from my musl translation + months += 1; + + if (months > 10) { + months -= 12; + years += 1; + } + Date { + year: (years + 2000) as u32, + month: (months + 2) as u32, + day_of_month: (remdays + 1) as u32, + day_of_week: (wday) as u32, + hour: (remsecs / 3600) as u32, + minute: ((remsecs / 60) % 60) as u32, + second: (remsecs % 60) as u32, + } +} + +const SOLAR_YEAR_SECS: u64 = 31556926; + +pub fn timeago(unixtime: u64) -> String { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + if unixtime > current_time { + return "in the future".to_owned(); + } + let diff = current_time - unixtime; + let amount: u64; + let metric: &str; + if diff < 60 { + amount = diff; + metric = "second"; + } else if diff < 60 * 60 { + amount = diff / 60; + metric = "minute"; + } else if diff < 60 * 60 * 24 { + amount = diff / (60 * 60); + metric = "hour"; + } else if diff < SOLAR_YEAR_SECS * 2 { + amount = diff / (60 * 60 * 24); + metric = "day"; + } else { + amount = diff / SOLAR_YEAR_SECS * 2; + metric = "year"; + } + match amount { + 1 => format!("{} {} ago", amount, metric), + _ => format!("{} {}s ago", amount, metric), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_date_conversion() { + println!("{:?}", secs_to_date(1640211435)); + println!("{:?}", secs_to_date(1641321435)) + } +} diff --git a/src/utils.rs b/src/utils.rs @@ -1,7 +1,5 @@ use linkify::{LinkFinder, LinkKind}; -use std::time::{SystemTime, UNIX_EPOCH}; -const SOLAR_YEAR_SECS: u64 = 31556926; // partly stolen from // https://github.com/robinst/linkify/blob/demo/src/lib.rs#L5 @@ -70,35 +68,3 @@ fn xml_escape(text: &str, dest: &mut Vec<u8>) { } } } -pub fn timeago(unixtime: u64) -> String { - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - if unixtime > current_time { - return "in the future".to_owned(); - } - let diff = current_time - unixtime; - let amount: u64; - let metric: &str; - if diff < 60 { - amount = diff; - metric = "second"; - } else if diff < 60 * 60 { - amount = diff / 60; - metric = "minute"; - } else if diff < 60 * 60 * 24 { - amount = diff / (60 * 60); - metric = "hour"; - } else if diff < SOLAR_YEAR_SECS * 2 { - amount = diff / (60 * 60 * 24); - metric = "day"; - } else { - amount = diff / SOLAR_YEAR_SECS * 2; - metric = "year"; - } - match amount { - 1 => format!("{} {} ago", amount, metric), - _ => format!("{} {}s ago", amount, metric), - } -}