1use crate::config::paths::Paths;
2use anyhow::{Context, Result};
3use std::fs;
4use std::path::PathBuf;
5use std::time::{Duration, SystemTime};
6
7pub fn prepare_log_directory(component: &str, use_date_subdir: bool) -> Result<PathBuf> {
15 let base_log_dir = Paths::in_state_dir("logs");
16
17 let _ = cleanup_old_logs(component);
18
19 let component_dir = base_log_dir.join(component);
20
21 let log_dir = if use_date_subdir {
22 component_dir.join(chrono::Local::now().format("%Y-%m-%d").to_string())
23 } else {
24 component_dir
25 };
26
27 fs::create_dir_all(&log_dir)
28 .with_context(|| format!("Failed to create log directory: {:?}", log_dir))?;
29
30 Ok(log_dir)
31}
32
33pub fn cleanup_old_logs(component: &str) -> Result<()> {
34 let base_log_dir = Paths::in_state_dir("logs");
35 let component_dir = base_log_dir.join(component);
36
37 if !component_dir.exists() {
38 return Ok(());
39 }
40
41 let two_weeks = SystemTime::now() - Duration::from_secs(14 * 24 * 60 * 60);
42 let entries = fs::read_dir(&component_dir)?;
43
44 for entry in entries.flatten() {
45 let path = entry.path();
46
47 if let Ok(metadata) = entry.metadata() {
48 if let Ok(modified) = metadata.modified() {
49 if modified < two_weeks && path.is_dir() {
50 let _ = fs::remove_dir_all(&path);
51 }
52 }
53 }
54 }
55
56 Ok(())
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use std::fs;
63
64 #[test]
65 fn test_get_log_directory_basic_functionality() {
66 let result = prepare_log_directory("cli", false);
68 assert!(result.is_ok());
69
70 let log_dir = result.unwrap();
71
72 assert!(log_dir.exists());
74 assert!(log_dir.is_dir());
75
76 let path_str = log_dir.to_string_lossy();
77 assert!(path_str.contains("cli"));
78 assert!(path_str.contains("logs"));
79
80 let test_file = log_dir.join("test.log");
82 assert!(fs::write(&test_file, "test log content").is_ok());
83 let _ = fs::remove_file(&test_file);
84 }
85
86 #[test]
87 fn test_get_log_directory_with_date_subdir() {
88 let result = prepare_log_directory("server", true);
90 assert!(result.is_ok());
91
92 let log_dir = result.unwrap();
93
94 assert!(log_dir.exists());
96 assert!(log_dir.is_dir());
97
98 let path_str = log_dir.to_string_lossy();
99 assert!(path_str.contains("server"));
100 assert!(path_str.contains("logs"));
101
102 let now = chrono::Local::now();
104 let date_str = now.format("%Y-%m-%d").to_string();
105 assert!(path_str.contains(&date_str));
106
107 let logs_pos = path_str.find("logs").unwrap();
109 let component_pos = path_str.find("server").unwrap();
110 let date_pos = path_str.find(&date_str).unwrap();
111 assert!(logs_pos < component_pos);
112 assert!(component_pos < date_pos);
113 }
114
115 #[test]
116 fn test_get_log_directory_idempotent() {
117 let component = "debug";
119
120 let result1 = prepare_log_directory(component, false);
121 assert!(result1.is_ok());
122 let log_dir1 = result1.unwrap();
123
124 let result2 = prepare_log_directory(component, false);
125 assert!(result2.is_ok());
126 let log_dir2 = result2.unwrap();
127
128 assert_eq!(log_dir1, log_dir2);
130 assert!(log_dir1.exists());
131 assert!(log_dir2.exists());
132
133 let result3 = prepare_log_directory(component, true);
135 assert!(result3.is_ok());
136 let log_dir3 = result3.unwrap();
137
138 let result4 = prepare_log_directory(component, true);
139 assert!(result4.is_ok());
140 let log_dir4 = result4.unwrap();
141
142 assert_eq!(log_dir3, log_dir4);
143 assert!(log_dir3.exists());
144 }
145
146 #[test]
147 fn test_get_log_directory_different_components() {
148 let components = ["cli", "server", "debug"];
150 let mut created_dirs = Vec::new();
151
152 for component in &components {
153 let result = prepare_log_directory(component, false);
154 assert!(result.is_ok(), "Failed for component: {}", component);
155
156 let log_dir = result.unwrap();
157 assert!(log_dir.exists());
158 assert!(log_dir.to_string_lossy().contains(component));
159
160 created_dirs.push(log_dir);
161 }
162
163 for i in 0..created_dirs.len() {
165 for j in i + 1..created_dirs.len() {
166 assert_ne!(created_dirs[i], created_dirs[j]);
167 }
168 }
169 }
170}