2026-03-03 17:23:43 -05:00
|
|
|
// Shared utilities
|
|
|
|
|
|
|
|
|
|
use crate::store;
|
|
|
|
|
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
/// Ensure a subdirectory of the memory dir exists and return its path.
|
2026-03-05 22:23:03 -05:00
|
|
|
pub fn memory_subdir(name: &str) -> Result<PathBuf, String> {
|
2026-03-03 17:23:43 -05:00
|
|
|
let dir = store::memory_dir().join(name);
|
|
|
|
|
fs::create_dir_all(&dir)
|
|
|
|
|
.map_err(|e| format!("create {}: {}", dir.display(), e))?;
|
|
|
|
|
Ok(dir)
|
|
|
|
|
}
|
2026-03-08 21:13:02 -04:00
|
|
|
|
|
|
|
|
/// Truncate text to `max_len` bytes at a char boundary, appending `suffix`.
|
|
|
|
|
/// Returns the original string if it's already short enough.
|
|
|
|
|
pub fn truncate(text: &str, max_len: usize, suffix: &str) -> String {
|
|
|
|
|
if text.len() <= max_len {
|
|
|
|
|
text.to_string()
|
|
|
|
|
} else {
|
|
|
|
|
let end = text.floor_char_boundary(max_len);
|
|
|
|
|
format!("{}{}", &text[..end], suffix)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Take the first `n` chars from a string.
|
|
|
|
|
pub fn first_n_chars(s: &str, n: usize) -> String {
|
|
|
|
|
s.chars().take(n).collect()
|
|
|
|
|
}
|
2026-03-08 21:22:05 -04:00
|
|
|
|
|
|
|
|
/// Parse a timestamp string to unix epoch seconds.
|
|
|
|
|
/// Handles: "2026-03-05T19:56:00", "2026-03-05T19:56", "2026-03-05 19:56:00", "2026-03-05 19:56"
|
|
|
|
|
pub fn parse_timestamp_to_epoch(ts: &str) -> Option<i64> {
|
|
|
|
|
use chrono::{Local, NaiveDateTime, TimeZone};
|
|
|
|
|
let formats = ["%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M"];
|
|
|
|
|
for fmt in &formats {
|
|
|
|
|
if let Ok(ndt) = NaiveDateTime::parse_from_str(ts, fmt) {
|
|
|
|
|
if let Some(dt) = Local.from_local_datetime(&ndt).earliest() {
|
|
|
|
|
return Some(dt.timestamp());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|