1use fuser::{FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyWrite};
4use libc::ENOENT;
5use std::path::PathBuf;
6use tracing::{info, warn};
7
8use super::{RagFs, TTL, reply_file_bytes};
9use crate::inode::{
10 APPROVE_FILE_INO, CLEANUP_FILE_INO, DEDUPE_FILE_INO, ORGANIZE_FILE_INO, PENDING_DIR_INO,
11 RAGFS_DIR_INO, REJECT_FILE_INO, SEMANTIC_DIR_INO, SIMILAR_OPS_FILE_INO,
12};
13
14impl RagFs {
15 pub(crate) fn lookup_semantic_dir(&mut self, name_str: &str, reply: ReplyEntry) {
16 match name_str {
17 ".organize" => {
18 let attr = self.make_attr(ORGANIZE_FILE_INO, FileType::RegularFile, 0);
19 reply.entry(&TTL, &attr, 0);
20 }
21 ".similar" => {
22 let content = self
23 .runtime
24 .block_on(self.semantic_manager.get_similar_json());
25 let attr = self.make_attr(
26 SIMILAR_OPS_FILE_INO,
27 FileType::RegularFile,
28 content.len() as u64,
29 );
30 let mut cache = self.runtime.block_on(self.content_cache.write());
31 cache.insert(SIMILAR_OPS_FILE_INO, content);
32 reply.entry(&TTL, &attr, 0);
33 }
34 ".cleanup" => {
35 let content = self
36 .runtime
37 .block_on(self.semantic_manager.get_cleanup_json());
38 let attr = self.make_attr(
39 CLEANUP_FILE_INO,
40 FileType::RegularFile,
41 content.len() as u64,
42 );
43 let mut cache = self.runtime.block_on(self.content_cache.write());
44 cache.insert(CLEANUP_FILE_INO, content);
45 reply.entry(&TTL, &attr, 0);
46 }
47 ".dedupe" => {
48 let content = self
49 .runtime
50 .block_on(self.semantic_manager.get_dedupe_json());
51 let attr =
52 self.make_attr(DEDUPE_FILE_INO, FileType::RegularFile, content.len() as u64);
53 let mut cache = self.runtime.block_on(self.content_cache.write());
54 cache.insert(DEDUPE_FILE_INO, content);
55 reply.entry(&TTL, &attr, 0);
56 }
57 ".pending" => {
58 let attr = self.make_attr(PENDING_DIR_INO, FileType::Directory, 0);
59 reply.entry(&TTL, &attr, 0);
60 }
61 ".approve" => {
62 let attr = self.make_attr(APPROVE_FILE_INO, FileType::RegularFile, 0);
63 reply.entry(&TTL, &attr, 0);
64 }
65 ".reject" => {
66 let attr = self.make_attr(REJECT_FILE_INO, FileType::RegularFile, 0);
67 reply.entry(&TTL, &attr, 0);
68 }
69 _ => reply.error(ENOENT),
70 }
71 }
72
73 pub(crate) fn lookup_pending_dir(&mut self, name_str: &str, reply: ReplyEntry) {
74 let plan_ids = self
75 .runtime
76 .block_on(self.semantic_manager.get_pending_plan_ids());
77 if plan_ids.contains(&name_str.to_string()) {
78 let content = self
79 .runtime
80 .block_on(self.semantic_manager.get_plan_json(name_str));
81 let mut inodes = self.runtime.block_on(self.inodes.write());
82 let ino = inodes.get_or_create_query_result(PENDING_DIR_INO, name_str.to_string());
83 let attr = self.make_attr(ino, FileType::RegularFile, content.len() as u64);
84 let mut cache = self.runtime.block_on(self.content_cache.write());
85 cache.insert(ino, content);
86 reply.entry(&TTL, &attr, 0);
87 return;
88 }
89 reply.error(ENOENT);
90 }
91
92 pub(crate) fn getattr_semantic(&mut self, ino: u64, reply: ReplyAttr) {
93 match ino {
94 SEMANTIC_DIR_INO | PENDING_DIR_INO => {
95 let attr = self.make_attr(ino, FileType::Directory, 0);
96 reply.attr(&TTL, &attr);
97 }
98 SIMILAR_OPS_FILE_INO => {
99 let content = self
100 .runtime
101 .block_on(self.semantic_manager.get_similar_json());
102 let size = content.len() as u64;
103 let mut cache = self.runtime.block_on(self.content_cache.write());
104 cache.insert(SIMILAR_OPS_FILE_INO, content);
105 let attr = self.make_attr(ino, FileType::RegularFile, size);
106 reply.attr(&TTL, &attr);
107 }
108 CLEANUP_FILE_INO => {
109 let content = self
110 .runtime
111 .block_on(self.semantic_manager.get_cleanup_json());
112 let size = content.len() as u64;
113 let mut cache = self.runtime.block_on(self.content_cache.write());
114 cache.insert(CLEANUP_FILE_INO, content);
115 let attr = self.make_attr(ino, FileType::RegularFile, size);
116 reply.attr(&TTL, &attr);
117 }
118 DEDUPE_FILE_INO => {
119 let content = self
120 .runtime
121 .block_on(self.semantic_manager.get_dedupe_json());
122 let size = content.len() as u64;
123 let mut cache = self.runtime.block_on(self.content_cache.write());
124 cache.insert(DEDUPE_FILE_INO, content);
125 let attr = self.make_attr(ino, FileType::RegularFile, size);
126 reply.attr(&TTL, &attr);
127 }
128 ORGANIZE_FILE_INO | APPROVE_FILE_INO | REJECT_FILE_INO => {
129 let attr = self.make_attr(ino, FileType::RegularFile, 0);
130 reply.attr(&TTL, &attr);
131 }
132 _ => reply.error(ENOENT),
133 }
134 }
135
136 pub(crate) fn read_semantic(&mut self, ino: u64, offset: i64, size: u32, reply: ReplyData) {
137 match ino {
138 SIMILAR_OPS_FILE_INO => {
139 let content = self
140 .runtime
141 .block_on(self.semantic_manager.get_similar_json());
142 reply_file_bytes(reply, &content, offset, size);
143 }
144 CLEANUP_FILE_INO => {
145 let content = self
146 .runtime
147 .block_on(self.semantic_manager.get_cleanup_json());
148 reply_file_bytes(reply, &content, offset, size);
149 }
150 DEDUPE_FILE_INO => {
151 let content = self
152 .runtime
153 .block_on(self.semantic_manager.get_dedupe_json());
154 reply_file_bytes(reply, &content, offset, size);
155 }
156 ORGANIZE_FILE_INO | APPROVE_FILE_INO | REJECT_FILE_INO => {
157 reply.data(&[]);
158 }
159 _ => reply.error(ENOENT),
160 }
161 }
162
163 pub(crate) fn readdir_semantic(&mut self, ino: u64, offset: i64, mut reply: ReplyDirectory) {
164 match ino {
165 SEMANTIC_DIR_INO => {
166 let entries = [
167 (SEMANTIC_DIR_INO, FileType::Directory, "."),
168 (RAGFS_DIR_INO, FileType::Directory, ".."),
169 (ORGANIZE_FILE_INO, FileType::RegularFile, ".organize"),
170 (SIMILAR_OPS_FILE_INO, FileType::RegularFile, ".similar"),
171 (CLEANUP_FILE_INO, FileType::RegularFile, ".cleanup"),
172 (DEDUPE_FILE_INO, FileType::RegularFile, ".dedupe"),
173 (PENDING_DIR_INO, FileType::Directory, ".pending"),
174 (APPROVE_FILE_INO, FileType::RegularFile, ".approve"),
175 (REJECT_FILE_INO, FileType::RegularFile, ".reject"),
176 ];
177
178 for (i, (ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
179 if reply.add(*ino, (i + 1) as i64, *kind, name) {
180 break;
181 }
182 }
183 reply.ok();
184 }
185 PENDING_DIR_INO => {
186 let mut entries: Vec<(u64, FileType, String)> = vec![
187 (PENDING_DIR_INO, FileType::Directory, ".".to_string()),
188 (SEMANTIC_DIR_INO, FileType::Directory, "..".to_string()),
189 ];
190
191 let plan_ids = self
192 .runtime
193 .block_on(self.semantic_manager.get_pending_plan_ids());
194 for plan_id in plan_ids {
195 let mut inodes = self.runtime.block_on(self.inodes.write());
196 let entry_ino =
197 inodes.get_or_create_query_result(PENDING_DIR_INO, plan_id.clone());
198 entries.push((entry_ino, FileType::RegularFile, plan_id));
199 }
200
201 for (i, (ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
202 if reply.add(*ino, (i + 1) as i64, *kind, name) {
203 break;
204 }
205 }
206 reply.ok();
207 }
208 _ => reply.error(ENOENT),
209 }
210 }
211
212 pub(crate) fn write_semantic(&mut self, ino: u64, data: &[u8], reply: ReplyWrite) {
213 if ino == ORGANIZE_FILE_INO {
214 let data_str = String::from_utf8_lossy(data).to_string();
215 let semantic_manager = self.semantic_manager.clone();
216 let result = self.runtime.block_on(async move {
217 match serde_json::from_str::<crate::semantic::OrganizeRequest>(&data_str) {
218 Ok(request) => semantic_manager.create_organize_plan(request).await,
219 Err(e) => Err(format!("Invalid OrganizeRequest JSON: {e}")),
220 }
221 });
222 match result {
223 Ok(plan) => info!("Created organization plan: {}", plan.id),
224 Err(e) => warn!("Failed to create organization plan: {}", e),
225 }
226 reply.written(data.len() as u32);
227 return;
228 }
229 if ino == SIMILAR_OPS_FILE_INO {
230 let data_str = String::from_utf8_lossy(data).trim().to_string();
231 let path = PathBuf::from(&data_str);
232 let semantic_manager = self.semantic_manager.clone();
233 let result = self
234 .runtime
235 .block_on(async move { semantic_manager.find_similar(&path).await });
236 match result {
237 Ok(r) => info!("Found {} similar files to {}", r.similar.len(), data_str),
238 Err(e) => warn!("Failed to find similar files: {}", e),
239 }
240 reply.written(data.len() as u32);
241 return;
242 }
243 if ino == APPROVE_FILE_INO {
244 let data_str = String::from_utf8_lossy(data).trim().to_string();
245 if let Ok(plan_id) = uuid::Uuid::parse_str(&data_str) {
246 let semantic_manager = self.semantic_manager.clone();
247 let result = self
248 .runtime
249 .block_on(async move { semantic_manager.approve_plan(plan_id).await });
250 match result {
251 Ok(plan) => info!("Approved plan: {}", plan.id),
252 Err(e) => warn!("Failed to approve plan: {}", e),
253 }
254 } else {
255 warn!("Invalid plan ID for approve: {}", data_str);
256 }
257 reply.written(data.len() as u32);
258 return;
259 }
260 if ino == REJECT_FILE_INO {
261 let data_str = String::from_utf8_lossy(data).trim().to_string();
262 if let Ok(plan_id) = uuid::Uuid::parse_str(&data_str) {
263 let semantic_manager = self.semantic_manager.clone();
264 let result = self
265 .runtime
266 .block_on(async move { semantic_manager.reject_plan(plan_id).await });
267 match result {
268 Ok(plan) => info!("Rejected plan: {}", plan.id),
269 Err(e) => warn!("Failed to reject plan: {}", e),
270 }
271 } else {
272 warn!("Invalid plan ID for reject: {}", data_str);
273 }
274 reply.written(data.len() as u32);
275 }
276 }
277}