use crate::config::YamlConfig;
use crate::constants::REPORT_DATE_FORMAT;
use crate::error;
use crate::info;
use chrono::NaiveDate;
use std::fs;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
pub(super) const DATE_FORMAT: &str = REPORT_DATE_FORMAT;
pub fn get_report_path(config: &YamlConfig) -> Option<String> {
let report_path = config.report_file_path();
if let Some(parent) = report_path.parent() {
let _ = fs::create_dir_all(parent);
}
if !report_path.exists() {
if let Err(e) = fs::write(&report_path, "") {
error!("创建日报文件失败: {}", e);
return None;
}
info!("已自动创建日报文件: {:?}", report_path);
}
Some(report_path.to_string_lossy().to_string())
}
pub fn get_report_path_silent(config: &YamlConfig) -> Option<String> {
let report_path = config.report_file_path();
if let Some(parent) = report_path.parent() {
let _ = fs::create_dir_all(parent);
}
if !report_path.exists() && fs::write(&report_path, "").is_err() {
return None;
}
Some(report_path.to_string_lossy().to_string())
}
pub fn get_settings_json_path(report_path: &str) -> Option<std::path::PathBuf> {
Path::new(report_path)
.parent()
.map(|p| p.join("settings.json"))
}
pub fn get_report_dir(config: &YamlConfig) -> Option<String> {
let report_path = config.report_file_path();
report_path
.parent()
.map(|p| p.to_string_lossy().to_string())
}
pub fn parse_date(s: &str) -> Option<NaiveDate> {
NaiveDate::parse_from_str(s, DATE_FORMAT).ok()
}
pub fn append_to_file(path: &Path, content: &str) {
use std::fs::OpenOptions;
use std::io::Write;
match OpenOptions::new().read(true).append(true).open(path) {
Ok(mut f) => {
let len = f.metadata().map(|m| m.len()).unwrap_or(0);
if len > 0 {
let _ = f.seek(SeekFrom::Start(len - 1));
let mut last_byte = [0u8; 1];
if f.read_exact(&mut last_byte).is_ok() && last_byte[0] != b'\n' {
let _ = f.write_all(b"\n");
}
let _ = f.seek(SeekFrom::End(0));
}
if let Err(e) = f.write_all(content.as_bytes()) {
error!("写入文件失败: {}", e);
}
}
Err(e) => error!("打开文件失败: {}", e),
}
}
pub fn replace_last_n_lines(path: &Path, n: usize, new_content: &str) {
let content = match fs::read_to_string(path) {
Ok(c) => c,
Err(e) => {
error!("读取文件失败: {}", e);
return;
}
};
let all_lines: Vec<&str> = content.lines().collect();
let keep_count = if all_lines.len() > n {
all_lines.len() - n
} else {
0
};
let mut result = String::new();
for line in &all_lines[..keep_count] {
result.push_str(line);
result.push('\n');
}
result.push_str(new_content);
if !result.ends_with('\n') {
result.push('\n');
}
if let Err(e) = fs::write(path, &result) {
error!("写入文件失败: {}", e);
}
}
pub fn read_last_n_lines(path: &Path, n: usize) -> Vec<String> {
let buffer_size: usize = crate::constants::REPORT_READ_BUFFER_SIZE; let mut lines = Vec::new();
let mut file = match fs::File::open(path) {
Ok(f) => f,
Err(e) => {
error!("读取文件时发生错误: {}", e);
return lines;
}
};
let file_len = match file.metadata() {
Ok(m) => m.len() as usize,
Err(_) => return lines,
};
if file_len == 0 {
return lines;
}
if n == usize::MAX || file_len <= buffer_size * 2 {
let mut content = String::new();
let _ = file.seek(SeekFrom::Start(0));
if file.read_to_string(&mut content).is_ok() {
let all_lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
if n >= all_lines.len() {
return all_lines;
}
return all_lines[all_lines.len() - n..].to_vec();
}
return lines;
}
let mut pointer = file_len;
let mut remainder = Vec::new();
while pointer > 0 && lines.len() < n {
let bytes_to_read = pointer.min(buffer_size);
pointer -= bytes_to_read;
let _ = file.seek(SeekFrom::Start(pointer as u64));
let mut buffer = vec![0u8; bytes_to_read];
if file.read_exact(&mut buffer).is_err() {
break;
}
buffer.append(&mut remainder);
let text = String::from_utf8_lossy(&buffer).to_string();
let mut block_lines: Vec<&str> = text.split('\n').collect();
if pointer > 0 {
remainder = block_lines.remove(0).as_bytes().to_vec();
}
for line in block_lines.into_iter().rev() {
if !line.is_empty() {
lines.push(line.to_string());
if lines.len() >= n {
break;
}
}
}
}
if !remainder.is_empty() && lines.len() < n {
let line = String::from_utf8_lossy(&remainder).to_string();
if !line.is_empty() {
lines.push(line);
}
}
lines.reverse();
lines
}