Skip to main content

ragfs_fuse/filesystem/
safety.rs

1//! `.safety/` virtual files.
2
3use fuser::{FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyWrite};
4use libc::ENOENT;
5use tracing::{info, warn};
6
7use super::{RagFs, TTL, reply_file_bytes};
8use crate::inode::{
9    FIRST_REAL_INO, HISTORY_FILE_INO, RAGFS_DIR_INO, SAFETY_DIR_INO, TRASH_DIR_INO, UNDO_FILE_INO,
10};
11
12impl RagFs {
13    pub(crate) fn lookup_safety_dir(&mut self, name_str: &str, reply: ReplyEntry) {
14        match name_str {
15            ".trash" => {
16                let attr = self.make_attr(TRASH_DIR_INO, FileType::Directory, 0);
17                reply.entry(&TTL, &attr, 0);
18            }
19            ".history" => {
20                let content = self.safety_manager.get_history_json(Some(100));
21                let attr = self.make_attr(
22                    HISTORY_FILE_INO,
23                    FileType::RegularFile,
24                    content.len() as u64,
25                );
26                let mut cache = self.runtime.block_on(self.content_cache.write());
27                cache.insert(HISTORY_FILE_INO, content);
28                reply.entry(&TTL, &attr, 0);
29            }
30            ".undo" => {
31                let attr = self.make_attr(UNDO_FILE_INO, FileType::RegularFile, 0);
32                reply.entry(&TTL, &attr, 0);
33            }
34            _ => reply.error(ENOENT),
35        }
36    }
37
38    pub(crate) fn getattr_safety(&mut self, ino: u64, reply: ReplyAttr) {
39        match ino {
40            SAFETY_DIR_INO | TRASH_DIR_INO => {
41                let attr = self.make_attr(ino, FileType::Directory, 0);
42                reply.attr(&TTL, &attr);
43            }
44            HISTORY_FILE_INO => {
45                let content = self.safety_manager.get_history_json(Some(100));
46                let size = content.len() as u64;
47                let mut cache = self.runtime.block_on(self.content_cache.write());
48                cache.insert(HISTORY_FILE_INO, content);
49                let attr = self.make_attr(ino, FileType::RegularFile, size);
50                reply.attr(&TTL, &attr);
51            }
52            UNDO_FILE_INO => {
53                let attr = self.make_attr(ino, FileType::RegularFile, 0);
54                reply.attr(&TTL, &attr);
55            }
56            _ => reply.error(ENOENT),
57        }
58    }
59
60    pub(crate) fn read_safety(&mut self, ino: u64, offset: i64, size: u32, reply: ReplyData) {
61        match ino {
62            HISTORY_FILE_INO => {
63                let content = self.safety_manager.get_history_json(Some(100));
64                reply_file_bytes(reply, &content, offset, size);
65            }
66            UNDO_FILE_INO => {
67                reply.data(&[]);
68            }
69            _ => reply.error(ENOENT),
70        }
71    }
72
73    pub(crate) fn readdir_safety(&mut self, ino: u64, offset: i64, mut reply: ReplyDirectory) {
74        match ino {
75            SAFETY_DIR_INO => {
76                let entries = [
77                    (SAFETY_DIR_INO, FileType::Directory, "."),
78                    (RAGFS_DIR_INO, FileType::Directory, ".."),
79                    (TRASH_DIR_INO, FileType::Directory, ".trash"),
80                    (HISTORY_FILE_INO, FileType::RegularFile, ".history"),
81                    (UNDO_FILE_INO, FileType::RegularFile, ".undo"),
82                ];
83
84                for (i, (ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
85                    if reply.add(*ino, (i + 1) as i64, *kind, name) {
86                        break;
87                    }
88                }
89                reply.ok();
90            }
91            TRASH_DIR_INO => {
92                let mut entries: Vec<(u64, FileType, String)> = vec![
93                    (TRASH_DIR_INO, FileType::Directory, ".".to_string()),
94                    (SAFETY_DIR_INO, FileType::Directory, "..".to_string()),
95                ];
96
97                let trash = self.runtime.block_on(self.safety_manager.list_trash());
98                for entry in trash {
99                    let entry_ino =
100                        FIRST_REAL_INO + 500_000 + (entry.id.as_u128() % 100_000) as u64;
101                    entries.push((entry_ino, FileType::RegularFile, entry.id.to_string()));
102                }
103
104                for (i, (ino, kind, name)) in entries.iter().enumerate().skip(offset as usize) {
105                    if reply.add(*ino, (i + 1) as i64, *kind, name) {
106                        break;
107                    }
108                }
109                reply.ok();
110            }
111            _ => reply.error(ENOENT),
112        }
113    }
114
115    pub(crate) fn write_safety(&mut self, _ino: u64, data: &[u8], reply: ReplyWrite) {
116        let data_str = String::from_utf8_lossy(data).trim().to_string();
117        if let Ok(operation_id) = uuid::Uuid::parse_str(&data_str) {
118            let safety_manager = self.safety_manager.clone();
119            let result = self
120                .runtime
121                .block_on(async move { safety_manager.undo(operation_id).await });
122            match result {
123                Ok(msg) => info!("Undo successful: {}", msg),
124                Err(e) => warn!("Undo failed: {}", e),
125            }
126        } else {
127            warn!("Invalid operation ID for undo: {}", data_str);
128        }
129        reply.written(data.len() as u32);
130    }
131}