1use std::collections::HashMap;
2use std::fs::{File, OpenOptions};
3use std::io::{BufRead, BufReader, Write};
4use std::path::{Path, PathBuf};
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex, RwLock};
7
8use crate::task::RetryableTask;
9
10#[derive(Clone)]
11pub struct FileStore {
12 file_path: Arc<PathBuf>,
13 total_tasks: Arc<Mutex<usize>>,
14 deleted_tasks: Arc<Mutex<usize>>,
15 append_count: Arc<Mutex<usize>>,
16 compacting: Arc<AtomicBool>,
17 file_lock: Arc<RwLock<()>>,
18 tasks_cache: Arc<RwLock<HashMap<String, RetryableTask>>>,
19}
20
21impl FileStore {
22 pub fn new<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
23 let fs = FileStore {
24 file_path: Arc::new(path.as_ref().to_path_buf()),
25 total_tasks: Arc::new(Mutex::new(0)),
26 deleted_tasks: Arc::new(Mutex::new(0)),
27 append_count: Arc::new(Mutex::new(0)),
28 compacting: Arc::new(AtomicBool::new(false)),
29 file_lock: Arc::new(RwLock::new(())),
30 tasks_cache: Arc::new(RwLock::new(HashMap::new())),
31 };
32 fs.rebuild_metadata()?;
33 Ok(fs)
34 }
35
36 fn rebuild_metadata(&self) -> std::io::Result<()> {
37 let _lock = self.file_lock.write().unwrap();
38 let mut cache = self.tasks_cache.write().unwrap();
39 cache.clear();
40
41 if !self.file_path.exists() {
42 return Ok(());
43 }
44
45 let file = File::open(self.file_path.as_ref())?;
46
47 let mut total = 0;
48 let mut deleted = 0;
49 let mut appended = 0;
50
51 let reader = BufReader::new(&file);
52 for line_str in reader.lines().map_while(Result::ok) {
53 if line_str.trim().is_empty() {
54 continue;
55 }
56 if let Ok(task) = serde_json::from_str::<RetryableTask>(&line_str) {
57 appended += 1;
58 if task.deleted_at.is_some() {
59 deleted += 1;
60 cache.remove(&task.task_id);
61 } else {
62 total += 1;
63 cache.insert(task.task_id.clone(), task);
64 }
65 }
66 }
67
68 *self.total_tasks.lock().unwrap() = total;
69 *self.deleted_tasks.lock().unwrap() = deleted;
70 *self.append_count.lock().unwrap() = appended;
71
72 Ok(())
73 }
74
75 pub fn file_path(&self) -> &Path {
76 self.file_path.as_ref()
77 }
78
79 pub fn save_task(&self, task: &RetryableTask) -> std::io::Result<()> {
80 self.save_task_inner(task, true)
81 }
82
83 fn save_task_inner(&self, task: &RetryableTask, check_compact: bool) -> std::io::Result<()> {
87 let _lock = self.file_lock.write().unwrap();
88
89 if let Some(parent) = self.file_path.parent() {
90 std::fs::create_dir_all(parent)?;
91 }
92
93 let mut file = OpenOptions::new()
94 .create(true)
95 .append(true)
96 .open(self.file_path.as_ref())?;
97
98 let json_str = serde_json::to_string(task)?;
99 writeln!(file, "{}", json_str)?;
100 file.sync_all()?;
101 drop(file);
102
103 let is_deleted = task.deleted_at.is_some();
104
105 {
106 let mut cache = self.tasks_cache.write().unwrap();
107 if is_deleted {
108 cache.remove(&task.task_id);
109 } else {
110 cache.insert(task.task_id.clone(), task.clone());
111 }
112 }
113
114 {
115 *self.append_count.lock().unwrap() += 1;
116 if is_deleted {
117 *self.deleted_tasks.lock().unwrap() += 1;
118 } else {
119 *self.total_tasks.lock().unwrap() += 1;
120 }
121 }
122
123 if check_compact && self.should_compact() {
124 let fs_clone = self.clone();
125 tokio::spawn(async move {
126 let _ = fs_clone.compact_log();
127 });
128 }
129
130 Ok(())
131 }
132
133 pub fn read_tasks(&self) -> std::io::Result<Vec<RetryableTask>> {
134 let cache = self.tasks_cache.read().unwrap();
135 Ok(cache.values().cloned().collect())
136 }
137
138 pub fn get_latest_task(&self, task_id: &str) -> std::io::Result<Option<RetryableTask>> {
139 let cache = self.tasks_cache.read().unwrap();
140 Ok(cache.get(task_id).cloned())
141 }
142
143 pub fn delete_task(&self, task_id: &str) -> std::io::Result<()> {
144 let mut task_opt = None;
145 {
146 let cache = self.tasks_cache.read().unwrap();
147 if let Some(task) = cache.get(task_id) {
148 if task.deleted_at.is_none() {
149 task_opt = Some(task.clone());
150 }
151 }
152 }
153
154 if let Some(mut task) = task_opt {
155 task.mark_deleted();
156 self.save_task_inner(&task, false)?;
159 }
160 Ok(())
161 }
162
163 fn should_compact(&self) -> bool {
164 if let Ok(metadata) = std::fs::metadata(self.file_path.as_ref()) {
165 if metadata.len() > 20 * 1024 * 1024 {
166 return true;
167 }
168 }
169
170 let total = *self.total_tasks.lock().unwrap();
171 let deleted = *self.deleted_tasks.lock().unwrap();
172 if total > 0 && (deleted as f64 / total as f64) > 0.5 {
173 return true;
174 }
175
176 if *self.append_count.lock().unwrap() >= 10000 {
177 return true;
178 }
179
180 false
181 }
182
183 pub fn compact_log(&self) -> std::io::Result<()> {
184 if self
185 .compacting
186 .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
187 .is_err()
188 {
189 return Ok(()); }
191
192 let temp_path = self.file_path.with_extension("tmp");
193
194 let result = (|| -> std::io::Result<()> {
195 let _lock = self.file_lock.write().unwrap();
196
197 let input_file = File::open(self.file_path.as_ref())?;
198
199 let mut temp_file = OpenOptions::new()
200 .create(true)
201 .write(true)
202 .truncate(true)
203 .open(&temp_path)?;
204
205 let mut task_map = HashMap::new();
206 let mut _total_tasks = 0;
207 let mut _deleted_count = 0;
208
209 let reader = BufReader::new(&input_file);
210 for line_str in reader.lines().map_while(Result::ok) {
211 if line_str.trim().is_empty() {
212 continue;
213 }
214 _total_tasks += 1;
215 if let Ok(task) = serde_json::from_str::<RetryableTask>(&line_str) {
216 if task.deleted_at.is_some() {
217 _deleted_count += 1;
218 task_map.remove(&task.task_id);
219 } else if let Some(existing) = task_map.get(&task.task_id) {
220 let existing_task: &RetryableTask = existing;
221 if task.updated_at > existing_task.updated_at {
222 task_map.insert(task.task_id.clone(), task);
223 }
224 } else {
225 task_map.insert(task.task_id.clone(), task);
226 }
227 }
228 }
229
230 for task in task_map.values() {
231 let json_str = serde_json::to_string(task)?;
232 writeln!(temp_file, "{}", json_str)?;
233 }
234
235 temp_file.sync_all()?;
236 std::fs::rename(&temp_path, self.file_path.as_ref())?;
237
238 *self.total_tasks.lock().unwrap() = task_map.len();
239 *self.deleted_tasks.lock().unwrap() = 0;
240 *self.append_count.lock().unwrap() = 0;
241
242 let mut cache = self.tasks_cache.write().unwrap();
244 cache.clear();
245 for (id, t) in task_map.into_iter() {
246 cache.insert(id, t);
247 }
248
249 Ok(())
250 })();
251
252 self.compacting.store(false, Ordering::SeqCst);
253 result
254 }
255}