1use std::env;
19use std::fs;
20use std::io;
21use std::net::IpAddr;
22use std::path::Path;
23use std::path::PathBuf;
24use std::time::Duration;
25use std::time::Instant;
26use std::time::SystemTime;
27use std::time::UNIX_EPOCH;
28
29use cheetah_string::CheetahString;
30use chrono::DateTime;
31use chrono::Datelike;
32use chrono::Local;
33use chrono::NaiveDateTime;
34use chrono::ParseError;
35use chrono::ParseResult;
36use chrono::TimeZone;
37use chrono::Timelike;
38use chrono::Utc;
39use local_ip_address::Error;
40use once_cell::sync::Lazy;
41use tracing::error;
42use tracing::info;
43
44use crate::common::mix_all::MULTI_PATH_SPLITTER;
45
46pub const YYYY_MM_DD_HH_MM_SS: &str = "%Y-%m-%d %H:%M:%S%";
47pub const YYYY_MM_DD_HH_MM_SS_SSS: &str = "%Y-%m-%d %H:%M:%S%.f";
48pub const YYYYMMDDHHMMSS: &str = "%Y%m%d%H%M%S%";
49
50const HEX_ARRAY: [char; 16] = [
51 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
52];
53
54pub fn compute_elapsed_time_milliseconds(begin_time: Instant) -> u64 {
55 let elapsed = begin_time.elapsed();
56 elapsed.as_millis() as u64
57}
58
59pub fn is_it_time_to_do(when: &str) -> bool {
60 let hours: Vec<&str> = when.split(";").collect();
61 if !hours.is_empty() {
62 let now = Local::now();
63 for hour in hours {
64 let now_hour: i32 = hour.parse().unwrap_or(0);
65 if now_hour == now.hour() as i32 {
66 return true;
67 }
68 }
69 }
70 false
71}
72
73pub fn time_millis_to_human_string2(t: i64) -> String {
85 let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
86
87 format!(
88 "{:04}-{:02}-{:02} {:02}:{:02}:{:02},{:03}",
89 dt.year(),
90 dt.month(),
91 dt.day(),
92 dt.hour(),
93 dt.minute(),
94 dt.second(),
95 dt.timestamp_subsec_millis()
96 )
97}
98
99pub fn time_millis_to_human_string3(t: i64) -> String {
111 let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
112
113 format!(
114 "{:04}{:02}{:02}{:02}{:02}{:02}",
115 dt.year(),
116 dt.month(),
117 dt.day(),
118 dt.hour(),
119 dt.minute(),
120 dt.second()
121 )
122}
123
124pub fn time_millis_to_human_string(t: i64) -> String {
136 let dt: DateTime<Local> = Local.timestamp_millis_opt(t).unwrap();
137
138 format!(
139 "{:04}{:02}{:02}{:02}{:02}{:02}{:03}",
140 dt.year(),
141 dt.month(),
142 dt.day(),
143 dt.hour(),
144 dt.minute(),
145 dt.second(),
146 dt.timestamp_subsec_millis()
147 )
148}
149
150pub fn is_path_exists(path: &str) -> bool {
151 Path::new(path).exists()
152}
153
154pub fn get_disk_partition_space_used_percent(path: &str) -> f64 {
155 if path.is_empty() {
156 error!(
157 "Error when measuring disk space usage, path is null or empty, path: {}",
158 path
159 );
160 return -1.0;
161 }
162
163 let path = Path::new(path);
164 if !path.exists() {
165 error!(
166 "Error when measuring disk space usage, file doesn't exist on this path: {}",
167 path.to_string_lossy()
168 );
169 return -1.0;
170 }
171
172 match fs::metadata(path) {
173 Ok(metadata) => {
174 let total_space = metadata.len();
175 if total_space > 0 {
176 match (fs::metadata(path), fs::metadata(path)) {
177 (Ok(metadata1), Ok(metadata2)) => {
178 let free_space = metadata1.len();
179 let usable_space = metadata2.len();
180 let used_space = total_space.saturating_sub(free_space);
181 let entire_space = used_space + usable_space;
182 let round_num = if used_space * 100 % entire_space != 0 {
183 1
184 } else {
185 0
186 };
187 let result = used_space * 100 / entire_space + round_num;
188 return result as f64 / 100.0;
189 }
190 (Err(e), _) | (_, Err(e)) => {
191 error!(
192 "Error when measuring disk space usage, got exception: {:?}",
193 e
194 );
195 return -1.0;
196 }
197 }
198 }
199 }
200 Err(e) => {
201 error!(
202 "Error when measuring disk space usage, got exception: {:?}",
203 e
204 );
205 return -1.0;
206 }
207 }
208
209 -1.0
210}
211
212pub fn bytes_to_string(src: &[u8]) -> String {
213 let mut hex_chars = Vec::with_capacity(src.len() * 2);
214 for &byte in src {
215 let v = byte as usize;
216 hex_chars.push(HEX_ARRAY[v >> 4]);
217 hex_chars.push(HEX_ARRAY[v & 0x0F]);
218 }
219 hex_chars.into_iter().collect()
220}
221
222pub fn write_int(buffer: &mut [char], pos: usize, value: i32) {
223 let mut current_pos = pos;
224 for move_bits in (0..=28).rev().step_by(4) {
225 buffer[current_pos] = HEX_ARRAY[((value >> move_bits) & 0xF) as usize];
226 current_pos += 1;
227 }
228}
229
230pub fn write_short(buffer: &mut [char], pos: usize, value: i16) {
231 let mut current_pos = pos;
232 for move_bits in (0..=12).rev().step_by(4) {
233 buffer[current_pos] = HEX_ARRAY[((value >> move_bits) & 0xF) as usize];
234 current_pos += 1;
235 }
236}
237
238pub fn string_to_bytes(hex_string: impl Into<String>) -> Option<Vec<u8>> {
239 let hex_string = hex_string.into();
240 if hex_string.is_empty() {
241 return None;
242 }
243
244 let hex_string = hex_string.to_uppercase();
245 let length = hex_string.len() / 2;
246 let mut bytes = Vec::<u8>::with_capacity(length);
247
248 for i in 0..length {
249 let pos = i * 2;
250 let byte = (char_to_byte(hex_string.chars().nth(pos)?) << 4)
251 | char_to_byte(hex_string.chars().nth(pos + 1)?);
252
253 bytes.push(byte);
254 }
255
256 Some(bytes)
257}
258
259#[inline]
270fn char_to_byte(c: char) -> u8 {
271 match c {
272 '0'..='9' => c as u8 - b'0',
273 'A'..='F' => c as u8 - b'A' + 10,
274 _ => 0,
275 }
276}
277
278#[inline]
296pub fn offset_to_file_name(offset: u64) -> String {
297 format!("{offset:020}")
298}
299
300pub fn ensure_dir_ok(dir_name: &str) {
301 if !dir_name.is_empty() {
302 let multi_path_splitter = MULTI_PATH_SPLITTER.as_str();
303 if dir_name.contains(multi_path_splitter) {
304 for dir in dir_name.trim().split(&multi_path_splitter) {
305 create_dir_if_not_exist(dir);
306 }
307 } else {
308 create_dir_if_not_exist(dir_name);
309 }
310 }
311}
312
313fn create_dir_if_not_exist(dir_name: &str) {
314 let path = Path::new(dir_name);
315 if !path.exists() {
316 match fs::create_dir_all(path) {
317 Ok(_) => info!("{} mkdir OK", dir_name),
318 Err(_) => info!("{} mkdir Failed", dir_name),
319 }
320 }
321}
322
323pub fn compute_next_minutes_time_millis() -> u64 {
324 let now = SystemTime::now();
325 let duration_since_epoch = now.duration_since(UNIX_EPOCH).unwrap();
326 let millis_since_epoch = duration_since_epoch.as_millis() as u64;
327
328 let millis_in_minute = 60 * 1000;
329 ((millis_since_epoch / millis_in_minute) + 1) * millis_in_minute
330}
331
332pub fn compute_next_morning_time_millis() -> u64 {
333 let now = Local::now();
334 let tomorrow = now.date_naive().succ_opt().unwrap();
335 let next_morning = Local
336 .with_ymd_and_hms(tomorrow.year(), tomorrow.month(), tomorrow.day(), 0, 0, 0)
337 .unwrap();
338 next_morning.timestamp_millis() as u64
339}
340
341pub fn delete_empty_directory<P: AsRef<Path>>(path: P) {
342 let path = path.as_ref();
343 if !path.exists() {
344 return;
345 }
346 if !path.is_dir() {
347 return;
348 }
349 match fs::read_dir(path) {
350 Ok(entries) => {
351 if entries.count() == 0 {
352 match fs::remove_dir(path) {
353 Ok(_) => info!("delete empty directory, {}", path.display()),
354 Err(e) => error!("Error deleting directory: {}", e),
355 }
356 }
357 }
358 Err(e) => error!("Error reading directory: {}", e),
359 }
360}
361
362pub fn get_ip() -> rocketmq_error::RocketMQResult<Vec<u8>> {
363 match local_ip_address::local_ip() {
364 Ok(value) => match value {
365 IpAddr::V4(ip) => Ok(ip.octets().to_vec()),
366 IpAddr::V6(ip) => Ok(ip.octets().to_vec()),
367 },
368 Err(_) => match local_ip_address::local_ipv6() {
369 Ok(value) => match value {
370 IpAddr::V4(ip) => Ok(ip.octets().to_vec()),
371 IpAddr::V6(ip) => Ok(ip.octets().to_vec()),
372 },
373 Err(value) => Err(rocketmq_error::RocketMQError::illegal_argument(format!(
374 "IP error: {}",
375 value
376 ))),
377 },
378 }
379}
380
381pub fn get_ip_str() -> CheetahString {
382 match local_ip_address::local_ip() {
383 Ok(value) => match value {
384 IpAddr::V4(ip) => CheetahString::from_string(ip.to_string()),
385 IpAddr::V6(ip) => CheetahString::from_string(ip.to_string()),
386 },
387 Err(_) => match local_ip_address::local_ipv6() {
388 Ok(value) => match value {
389 IpAddr::V4(ip) => CheetahString::from_string(ip.to_string()),
390 IpAddr::V6(ip) => CheetahString::from_string(ip.to_string()),
391 },
392 Err(value) => CheetahString::empty(),
393 },
394 }
395}
396
397pub fn parse_date(date: &str, pattern: &str) -> Option<NaiveDateTime> {
398 NaiveDateTime::parse_from_str(date, pattern).ok()
399}
400
401#[cfg(test)]
402mod tests {
403 use std::time::Instant;
404
405 use super::*;
406
407 #[test]
408 fn compute_elapsed_time_milliseconds_returns_correct_duration() {
409 let start = Instant::now();
410 std::thread::sleep(std::time::Duration::from_millis(100));
411 let elapsed = compute_elapsed_time_milliseconds(start);
412 assert!(elapsed >= 100);
413 }
414
415 #[test]
416 fn is_it_time_to_do_returns_true_when_current_hour_is_in_input() {
417 let current_hour = Local::now().hour();
418 assert!(is_it_time_to_do(¤t_hour.to_string()));
419 }
420
421 #[test]
422 fn is_it_time_to_do_returns_false_when_current_hour_is_not_in_input() {
423 let current_hour = (Local::now().hour() + 1) % 24;
424 assert!(!is_it_time_to_do(¤t_hour.to_string()));
425 }
426
427 #[test]
428 fn time_millis_to_human_string_formats_correctly() {
429 let timestamp = 1743239631601;
430 let expected = Local
431 .timestamp_millis_opt(timestamp)
432 .unwrap()
433 .format("%Y%m%d%H%M%S%3f")
434 .to_string();
435 assert_eq!(time_millis_to_human_string(timestamp), expected);
436 }
437
438 #[test]
439 fn is_path_exists_returns_true_for_existing_path() {
440 assert!(is_path_exists("."));
441 }
442
443 #[test]
444 fn is_path_exists_returns_false_for_non_existing_path() {
445 assert!(!is_path_exists("./non_existing_path"));
446 }
447
448 #[test]
449 fn bytes_to_string_converts_correctly() {
450 let bytes = [0x41, 0x42, 0x43];
451 assert_eq!(bytes_to_string(&bytes), "414243");
452 }
453
454 #[test]
455 fn offset_to_file_name_formats_correctly() {
456 assert_eq!(offset_to_file_name(123), "00000000000000000123");
457 }
458
459 #[test]
460 fn ensure_dir_ok_creates_directory_if_not_exists() {
461 let dir_name = "./test_dir";
462 ensure_dir_ok(dir_name);
463 assert!(is_path_exists(dir_name));
464 std::fs::remove_dir(dir_name).unwrap();
465 }
466
467 #[test]
468 fn test_compute_next_minutes_time_millis() {
469 let next_minute = compute_next_minutes_time_millis();
470 let now = SystemTime::now()
471 .duration_since(UNIX_EPOCH)
472 .unwrap()
473 .as_millis() as u64;
474
475 assert!(next_minute > now);
476 assert_eq!(next_minute % (60 * 1000), 0);
477 }
478
479 #[test]
490 fn time_millis_to_human_string2_formats_correctly_with_valid_timestamp() {
491 use chrono::TimeZone;
492 use chrono::Utc;
493 let timestamp = 1625140800000;
494 let expected = Local
495 .timestamp_millis_opt(timestamp)
496 .unwrap()
497 .format("%Y-%m-%d %H:%M:%S,%3f")
498 .to_string();
499 assert_eq!(time_millis_to_human_string2(timestamp), expected);
500 }
501
502 #[test]
503 fn time_millis_to_human_string3_formats_correctly_with_valid_timestamp() {
504 let timestamp = 1625140800000;
505 let expect = Local
506 .timestamp_millis_opt(timestamp)
507 .unwrap()
508 .format("%Y%m%d%H%M%S")
509 .to_string();
510 assert_eq!(time_millis_to_human_string3(timestamp), expect);
511 }
512}