Browse Source

Match Go TTL parsing: all-digit default to minutes, two-pass fit algorithm

rust-volume-server
Chris Lu 3 days ago
parent
commit
f6867b3dde
  1. 167
      seaweed-volume/src/storage/needle/ttl.rs

167
seaweed-volume/src/storage/needle/ttl.rs

@ -72,23 +72,30 @@ impl TTL {
}
/// Parse from string like "3m", "4h", "5d", "6w", "7M", "8y".
/// If the string is all digits (no unit suffix), defaults to minutes.
pub fn read(s: &str) -> Result<Self, String> {
let s = s.trim();
if s.is_empty() {
return Ok(TTL::EMPTY);
}
let (num_str, unit_char) = s.split_at(s.len() - 1);
let last_byte = s.as_bytes()[s.len() - 1];
let (num_str, unit_byte) = if last_byte >= b'0' && last_byte <= b'9' {
// All digits — default to minutes (matching Go)
(s, b'm')
} else {
(&s[..s.len() - 1], last_byte)
};
let count: u32 = num_str
.parse()
.map_err(|e| format!("invalid TTL count: {}", e))?;
let unit = match unit_char {
"m" => TTL_UNIT_MINUTE,
"h" => TTL_UNIT_HOUR,
"d" => TTL_UNIT_DAY,
"w" => TTL_UNIT_WEEK,
"M" => TTL_UNIT_MONTH,
"y" => TTL_UNIT_YEAR,
_ => return Err(format!("unknown TTL unit: {}", unit_char)),
let unit = match unit_byte {
b'm' => TTL_UNIT_MINUTE,
b'h' => TTL_UNIT_HOUR,
b'd' => TTL_UNIT_DAY,
b'w' => TTL_UNIT_WEEK,
b'M' => TTL_UNIT_MONTH,
b'y' => TTL_UNIT_YEAR,
_ => return Err(format!("unknown TTL unit: {}", unit_byte as char)),
};
Ok(fit_ttl_count(count, unit))
}
@ -112,102 +119,58 @@ fn unit_to_seconds(count: u64, unit: u8) -> u64 {
}
}
/// Fit a count into a single byte, converting to larger unit if needed.
/// Fit a count+unit into a TTL that fits in a single byte count.
/// Matches Go's fitTtlCount: convert to total seconds, then find the best-fit unit.
fn fit_ttl_count(count: u32, unit: u8) -> TTL {
if count <= 255 {
return TTL {
count: count as u8,
unit,
};
let seconds = unit_to_seconds(count as u64, unit);
if seconds == 0 {
return TTL::EMPTY;
}
// Try next larger unit
match unit {
TTL_UNIT_MINUTE => {
if count / 60 <= 255 {
return TTL {
count: (count / 60) as u8,
unit: TTL_UNIT_HOUR,
};
}
if count / (60 * 24) <= 255 {
return TTL {
count: (count / (60 * 24)) as u8,
unit: TTL_UNIT_DAY,
};
}
TTL {
count: 255,
unit: TTL_UNIT_DAY,
}
}
TTL_UNIT_HOUR => {
if count / 24 <= 255 {
return TTL {
count: (count / 24) as u8,
unit: TTL_UNIT_DAY,
};
}
TTL {
count: 255,
unit: TTL_UNIT_DAY,
}
}
TTL_UNIT_DAY => {
if count / 7 <= 255 {
return TTL {
count: (count / 7) as u8,
unit: TTL_UNIT_WEEK,
};
}
if count / 30 <= 255 {
return TTL {
count: (count / 30) as u8,
unit: TTL_UNIT_MONTH,
};
}
if count / 365 <= 255 {
return TTL {
count: (count / 365) as u8,
unit: TTL_UNIT_YEAR,
};
}
TTL {
count: 255,
unit: TTL_UNIT_YEAR,
}
}
TTL_UNIT_WEEK => {
if count * 7 / 30 <= 255 {
return TTL {
count: (count * 7 / 30) as u8,
unit: TTL_UNIT_MONTH,
};
}
if count * 7 / 365 <= 255 {
return TTL {
count: (count * 7 / 365) as u8,
unit: TTL_UNIT_YEAR,
};
}
TTL {
count: 255,
unit: TTL_UNIT_YEAR,
}
}
TTL_UNIT_MONTH => {
if count / 12 <= 255 {
return TTL {
count: (count / 12) as u8,
unit: TTL_UNIT_YEAR,
};
}
TTL {
count: 255,
unit: TTL_UNIT_YEAR,
}
}
_ => TTL { count: 255, unit },
const YEAR_SECS: u64 = 3600 * 24 * 365;
const MONTH_SECS: u64 = 3600 * 24 * 30;
const WEEK_SECS: u64 = 3600 * 24 * 7;
const DAY_SECS: u64 = 3600 * 24;
const HOUR_SECS: u64 = 3600;
const MINUTE_SECS: u64 = 60;
// First pass: try exact fits from largest to smallest
if seconds % YEAR_SECS == 0 && seconds / YEAR_SECS < 256 {
return TTL { count: (seconds / YEAR_SECS) as u8, unit: TTL_UNIT_YEAR };
}
if seconds % MONTH_SECS == 0 && seconds / MONTH_SECS < 256 {
return TTL { count: (seconds / MONTH_SECS) as u8, unit: TTL_UNIT_MONTH };
}
if seconds % WEEK_SECS == 0 && seconds / WEEK_SECS < 256 {
return TTL { count: (seconds / WEEK_SECS) as u8, unit: TTL_UNIT_WEEK };
}
if seconds % DAY_SECS == 0 && seconds / DAY_SECS < 256 {
return TTL { count: (seconds / DAY_SECS) as u8, unit: TTL_UNIT_DAY };
}
if seconds % HOUR_SECS == 0 && seconds / HOUR_SECS < 256 {
return TTL { count: (seconds / HOUR_SECS) as u8, unit: TTL_UNIT_HOUR };
}
// Minutes: truncating division
if seconds / MINUTE_SECS < 256 {
return TTL { count: (seconds / MINUTE_SECS) as u8, unit: TTL_UNIT_MINUTE };
}
// Second pass: truncating division from smallest to largest
if seconds / HOUR_SECS < 256 {
return TTL { count: (seconds / HOUR_SECS) as u8, unit: TTL_UNIT_HOUR };
}
if seconds / DAY_SECS < 256 {
return TTL { count: (seconds / DAY_SECS) as u8, unit: TTL_UNIT_DAY };
}
if seconds / WEEK_SECS < 256 {
return TTL { count: (seconds / WEEK_SECS) as u8, unit: TTL_UNIT_WEEK };
}
if seconds / MONTH_SECS < 256 {
return TTL { count: (seconds / MONTH_SECS) as u8, unit: TTL_UNIT_MONTH };
}
if seconds / YEAR_SECS < 256 {
return TTL { count: (seconds / YEAR_SECS) as u8, unit: TTL_UNIT_YEAR };
}
TTL::EMPTY
}
fn unit_to_char(unit: u8) -> char {

Loading…
Cancel
Save