1use anyhow::{Context, Result};
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4use std::path::Path;
5
6pub fn read_file_from_end<P: AsRef<Path>>(path: P, _buffer_size: usize) -> Result<Vec<String>> {
8 let file = File::open(&path)
9 .with_context(|| format!("Failed to open file: {}", path.as_ref().display()))?;
10
11 let reader = BufReader::new(file);
12 let mut lines = Vec::new();
13
14 for line_result in reader.lines() {
15 let line = line_result?;
16 if !line.trim().is_empty() {
17 lines.push(line.trim().to_string());
18 }
19 }
20
21 Ok(lines)
22}
23
24pub fn is_file_readable<P: AsRef<Path>>(path: P) -> bool {
26 File::open(path).is_ok()
27}
28
29pub fn get_file_size<P: AsRef<Path>>(path: P) -> Result<u64> {
31 let metadata = std::fs::metadata(path).with_context(|| "Failed to get file metadata")?;
32 Ok(metadata.len())
33}
34
35pub fn is_file_rotated<P: AsRef<Path>>(path: P, previous_size: u64) -> Result<bool> {
37 let current_size = get_file_size(path)?;
38 Ok(current_size < previous_size)
39}
40
41pub fn validate_files<P: AsRef<Path> + Clone>(files: &[P]) -> Result<Vec<P>> {
43 let mut valid_files = Vec::new();
44 let mut errors = Vec::new();
45
46 for file in files {
47 if is_file_readable(file) {
48 valid_files.push(file.clone());
49 } else {
50 errors.push(format!("File not readable: {}", file.as_ref().display()));
51 }
52 }
53
54 if valid_files.is_empty() {
55 return Err(anyhow::anyhow!(
56 "No valid files to watch: {}",
57 errors.join(", ")
58 ));
59 }
60
61 if !errors.is_empty() {
62 eprintln!(
63 "Warning: Some files are not accessible: {}",
64 errors.join(", ")
65 );
66 }
67
68 Ok(valid_files)
69}
70
71pub fn format_file_size(size: u64) -> String {
73 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
74 let mut size = size as f64;
75 let mut unit_index = 0;
76
77 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
78 size /= 1024.0;
79 unit_index += 1;
80 }
81
82 if unit_index == 0 {
83 format!("{} {}", size as u64, UNITS[unit_index])
84 } else {
85 format!("{:.1} {}", size, UNITS[unit_index])
86 }
87}
88
89pub fn get_filename<P: AsRef<Path>>(path: P) -> String {
91 path.as_ref()
92 .file_name()
93 .and_then(|name| name.to_str())
94 .unwrap_or("unknown")
95 .to_string()
96}
97
98pub fn is_symlink<P: AsRef<Path>>(path: P) -> bool {
100 path.as_ref()
101 .symlink_metadata()
102 .map(|metadata| metadata.file_type().is_symlink())
103 .unwrap_or(false)
104}
105
106pub fn resolve_symlink<P: AsRef<Path>>(path: P) -> Result<std::path::PathBuf> {
108 let resolved = path
109 .as_ref()
110 .read_link()
111 .with_context(|| format!("Failed to read symlink: {}", path.as_ref().display()))?;
112 Ok(resolved)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use std::io::Write;
119 use tempfile::NamedTempFile;
120
121 #[test]
122 fn test_format_file_size() {
123 assert_eq!(format_file_size(1024), "1.0 KB");
124 assert_eq!(format_file_size(1536), "1.5 KB");
125 assert_eq!(format_file_size(1048576), "1.0 MB");
126 assert_eq!(format_file_size(1023), "1023 B");
127 }
128
129 #[test]
130 fn test_get_filename() {
131 assert_eq!(get_filename("/path/to/file.log"), "file.log");
132 assert_eq!(get_filename("file.log"), "file.log");
133 assert_eq!(get_filename("/"), "unknown");
134 }
135
136 #[test]
137 fn test_validate_files() {
138 let temp_file = NamedTempFile::new().unwrap();
140 let temp_path = temp_file.path();
141
142 let valid_files = vec![temp_path];
144 let result = validate_files(&valid_files);
145 assert!(result.is_ok());
146 assert_eq!(result.unwrap().len(), 1);
147
148 let invalid_files = vec![std::path::Path::new("/nonexistent/file.log")];
150 let result = validate_files(&invalid_files);
151 assert!(result.is_err());
152 }
153
154 #[test]
155 fn test_read_file_from_end() {
156 let mut temp_file = NamedTempFile::new().unwrap();
157 writeln!(temp_file, "line 1").unwrap();
158 writeln!(temp_file, "line 2").unwrap();
159 writeln!(temp_file, "line 3").unwrap();
160 temp_file.flush().unwrap();
161
162 let lines = read_file_from_end(temp_file.path(), 1024).unwrap();
163 assert_eq!(lines.len(), 3);
164 assert_eq!(lines[0], "line 1");
165 assert_eq!(lines[1], "line 2");
166 assert_eq!(lines[2], "line 3");
167 }
168}