use std::fs;
use std::io;
use std::path::PathBuf;
use std::time::SystemTime;
#[inline]
pub(crate) fn format_u8_as_padded_2_digits(i: u8, buf: &mut [u8; 3]) -> &[u8] {
match i {
0 => b"00",
1 => b"01",
2 => b"02",
3 => b"03",
4 => b"04",
5 => b"05",
6 => b"06",
7 => b"07",
8 => b"08",
9 => b"09",
_ => proc_tools_core::utils_core::impl_to_ascii::itoa_buf_u8(buf, i),
}
}
#[inline(always)]
pub(crate) fn format_u16_as_padded_3_digits(i: u16, buf: &mut [u8; 5]) -> &[u8] {
if i < 10 {
buf[0] = b'0';
buf[1] = b'0';
buf[2] = b'0' + i as u8;
&buf[0..3]
} else if i < 100 {
buf[0] = b'0';
buf[1] = b'0' + (i / 10) as u8; buf[2] = b'0' + (i % 10) as u8; &buf[0..3]
} else {
proc_tools_core::utils_core::impl_to_ascii::itoa_buf_u16(buf, i)
}
}
#[inline(always)]
pub fn timestamp_ms_to_datetime(timestamp: u128) -> (u32, u8, u8, u8, u8, u8, u16) {
let total_seconds = timestamp / 1000;
let milliseconds = timestamp % 1000;
let days = total_seconds / 86400;
let seconds_in_day = total_seconds % 86400;
let hours = (seconds_in_day / 3600) as u8;
let minutes = ((seconds_in_day % 3600) / 60) as u8;
let seconds = (seconds_in_day % 60) as u8;
let n400 = days / 146097; let mut year = 1970 + n400 * 400;
let mut remaining_days = days % 146097;
let n100 = remaining_days / 36524; year += n100 * 100;
remaining_days %= 36524;
let n4 = remaining_days / 1461; year += n4 * 4;
remaining_days %= 1461;
for _ in 0..3 {
let is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
if remaining_days >= 366 && is_leap {
remaining_days -= 366;
year += 1;
} else if remaining_days >= 365 {
remaining_days -= 365;
year += 1;
} else {
break;
}
}
let mut month = 1;
let mut days_in_month = 31;
while remaining_days >= days_in_month {
remaining_days -= days_in_month;
month += 1;
days_in_month = match month {
2 => {
if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
29
} else {
28
}
}
4 | 6 | 9 | 11 => 30,
_ => 31,
};
}
let day = (remaining_days + 1) as u8;
(
year as u32,
month,
day,
hours,
minutes,
seconds,
milliseconds as u16,
)
}
#[inline]
pub(crate) fn find_log_files(dir_path: &str, pattern: &str) -> io::Result<Vec<PathBuf>> {
let fragments = parse_pattern(pattern);
let mut files: Vec<(PathBuf, SystemTime)> = Vec::new();
for entry in fs::read_dir(dir_path)? {
let entry = entry?;
let path = entry.path();
if !path.is_file() || path.file_name().is_none() {
continue;
}
let file_name_lossy = path.file_name().unwrap().to_string_lossy();
if !matches_pattern_bytes(&file_name_lossy, &fragments) {
continue;
}
let mod_time = fs::metadata(&path)?.modified()?;
files.push((path, mod_time));
}
files.sort_by(|a, b| b.1.cmp(&a.1));
Ok(files.into_iter().map(|(path, _)| path).collect())
}
#[inline(always)]
fn parse_pattern(pattern: &str) -> Vec<Fragment> {
let mut fragments: Vec<Fragment> = Vec::new();
let mut i: usize = 0;
let mut poi: usize = 0;
let bytes = pattern.as_bytes();
while i < pattern.len() {
if bytes[i] == b'%' {
if i + 1 == pattern.len() {
break;
}
let next_byte = bytes[i + 1];
let char_len = utf8_char_len(next_byte);
if char_len == 1 {
fragments.push(Fragment::Fixed(Box::from(&pattern[poi..i])));
if next_byte == b'Y' {
fragments.push(Fragment::PlaceholderFourDigits);
} else if next_byte == b'm'
|| next_byte == b'd'
|| next_byte == b'H'
|| next_byte == b'M'
|| next_byte == b'S'
{
fragments.push(Fragment::PlaceholderTwoDigits);
} else if next_byte == b'i' {
fragments.push(Fragment::PlaceholderAnyDigits);
} else {
i += 1;
fragments.pop();
continue;
}
i += 2;
poi = i;
} else {
let actual_len = char_len.min(bytes.len() - i);
i += actual_len;
}
} else {
i += 1;
}
}
if poi < pattern.len() {
fragments.push(Fragment::Fixed(Box::from(&pattern[poi..pattern.len()])));
}
fragments
}
enum Fragment {
PlaceholderFourDigits,
PlaceholderTwoDigits,
PlaceholderAnyDigits,
Fixed(Box<str>),
}
#[inline(always)]
fn utf8_char_len(next_byte: u8) -> usize {
if next_byte & 0b1110_0000 == 0b1100_0000 {
2
} else if next_byte & 0b1111_0000 == 0b1110_0000 {
3
} else if next_byte & 0b1111_1000 == 0b1111_0000 {
4
} else {
1 }
}
#[inline(always)]
fn matches_pattern_bytes(file_name: &str, fragments: &[Fragment]) -> bool {
let mut pos = 0;
let file_name_bytes = file_name.as_bytes();
for fragment in fragments {
match fragment {
Fragment::Fixed(s) => {
if !file_name_bytes[pos..pos + s.len()].starts_with(s.as_bytes()) {
return false;
}
pos += s.len();
}
Fragment::PlaceholderTwoDigits => {
if !file_name_bytes[pos].is_ascii_digit() {
return false;
}
pos += 1;
if !file_name_bytes[pos].is_ascii_digit() {
return false;
}
pos += 1;
}
Fragment::PlaceholderFourDigits => {
for ch in &file_name_bytes[pos..pos + 4] {
if !ch.is_ascii_digit() {
return false;
}
}
pos += 4;
}
Fragment::PlaceholderAnyDigits => {
let remain_str = &file_name_bytes[pos..];
let remain_len = remain_str.len().min(39);
let mut any_digits = 0;
for i in &file_name_bytes[pos..pos + remain_len] {
if !i.is_ascii_digit() {
break;
}
any_digits += 1;
}
if any_digits == 0 {
return false;
}
pos += any_digits;
}
}
}
pos == file_name_bytes.len()
}