94 lines
2.8 KiB
Rust
94 lines
2.8 KiB
Rust
//! Append-only diagnostic files, independent of resumable state.
|
|
use std::io::{self, Write};
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
static NEXT: AtomicU64 = AtomicU64::new(0);
|
|
|
|
pub fn date() -> String {
|
|
let days = std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs()
|
|
/ 86400;
|
|
date_from_days(days as i64)
|
|
}
|
|
fn date_from_days(days: i64) -> String {
|
|
// Gregorian civil date from Unix days (400-year eras).
|
|
let z = days + 719468;
|
|
let era = z / 146097;
|
|
let doe = z - era * 146097;
|
|
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
|
let y = yoe + era * 400;
|
|
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
let mp = (5 * doy + 2) / 153;
|
|
let d = doy - (153 * mp + 2) / 5 + 1;
|
|
let m = mp + if mp < 10 { 3 } else { -9 };
|
|
format!("{:04}-{m:02}-{d:02}", y + i64::from(m <= 2))
|
|
}
|
|
|
|
pub fn create(name: &str, extension: &str) -> io::Result<(String, std::fs::File)> {
|
|
let dir = format!("/logs/box/{}", date());
|
|
std::fs::create_dir_all(&dir)?;
|
|
let name: String = name
|
|
.chars()
|
|
.map(|c| {
|
|
if c.is_ascii_alphanumeric() || c == '-' {
|
|
c
|
|
} else {
|
|
'_'
|
|
}
|
|
})
|
|
.collect();
|
|
let mut path = format!("{dir}/{name}.{extension}");
|
|
loop {
|
|
match std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(&path)
|
|
{
|
|
Ok(f) => return Ok((path, f)),
|
|
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
|
|
path = format!(
|
|
"{dir}/{name}-{}-{}.{extension}",
|
|
crate::pid(),
|
|
NEXT.fetch_add(1, Ordering::Relaxed)
|
|
);
|
|
}
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn save(area: &str, name: &str, bytes: &[u8]) -> io::Result<String> {
|
|
let dir = format!("/logs/{area}/{}", date());
|
|
std::fs::create_dir_all(&dir)?;
|
|
loop {
|
|
let path = format!(
|
|
"{dir}/{name}-{}-{}.log",
|
|
crate::pid(),
|
|
NEXT.fetch_add(1, Ordering::Relaxed)
|
|
);
|
|
match std::fs::OpenOptions::new()
|
|
.write(true)
|
|
.create_new(true)
|
|
.open(&path)
|
|
{
|
|
Ok(mut f) => {
|
|
f.write_all(bytes)?;
|
|
return Ok(path);
|
|
}
|
|
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn archive_calendar_dates() {
|
|
assert_eq!(date_from_days(0), "1970-01-01");
|
|
assert_eq!(date_from_days(19782), "2024-02-29");
|
|
assert_eq!(date_from_days(20635), "2026-07-01");
|
|
}
|
|
}
|