Skip to main content

oak_vfs/vfs/
disk.rs

1use crate::{FileMetadata, FileType, Vfs, WritableVfs};
2use oak_core::{
3    Arc,
4    source::{SourceId, SourceText},
5};
6use std::{
7    collections::HashMap,
8    fs,
9    path::PathBuf,
10    sync::{
11        RwLock,
12        atomic::{AtomicU32, Ordering},
13    },
14};
15use url::Url;
16
17/// A disk-based Virtual File System implementation.
18/// Handles file URIs and interacts with the physical file system.
19#[derive(Default)]
20pub struct DiskVfs {
21    ids: std::sync::Arc<RwLock<HashMap<SourceId, Arc<str>>>>,
22    uri_to_id: std::sync::Arc<RwLock<HashMap<Arc<str>, SourceId>>>,
23    next_id: std::sync::Arc<AtomicU32>,
24}
25
26impl Clone for DiskVfs {
27    fn clone(&self) -> Self {
28        Self { ids: self.ids.clone(), uri_to_id: self.uri_to_id.clone(), next_id: self.next_id.clone() }
29    }
30}
31
32impl DiskVfs {
33    /// Create a new DiskVfs.
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Convert a URI string to a PathBuf.
39    fn uri_to_path(&self, uri: &str) -> Option<PathBuf> {
40        if let Ok(url) = Url::parse(uri) {
41            if url.scheme() == "file" {
42                return url.to_file_path().ok();
43            }
44        }
45        // Fallback for relative paths or non-URI strings
46        Some(PathBuf::from(uri))
47    }
48
49    /// Convert a PathBuf to a URI string.
50    fn path_to_uri(&self, path: PathBuf) -> Arc<str> {
51        if let Ok(url) = Url::from_file_path(path) { Arc::from(url.to_string()) } else { Arc::from("") }
52    }
53
54    fn get_or_assign_id(&self, uri: &str) -> SourceId {
55        let mut uri_to_id = self.uri_to_id.write().unwrap();
56        if let Some(id) = uri_to_id.get(uri) {
57            return *id;
58        }
59
60        let mut ids = self.ids.write().unwrap();
61        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
62        let uri_arc: Arc<str> = Arc::from(uri);
63        uri_to_id.insert(uri_arc.clone(), id);
64        ids.insert(id, uri_arc);
65        id
66    }
67}
68
69impl Vfs for DiskVfs {
70    type Source = SourceText;
71
72    fn get_source(&self, uri: &str) -> Option<SourceText> {
73        let path = self.uri_to_path(uri)?;
74        let id = self.get_or_assign_id(uri);
75        fs::read_to_string(path).ok().map(|s| SourceText::new_with_id(s, id))
76    }
77
78    fn get_uri(&self, id: SourceId) -> Option<Arc<str>> {
79        let ids = self.ids.read().unwrap();
80        ids.get(&id).cloned()
81    }
82
83    fn get_id(&self, uri: &str) -> Option<SourceId> {
84        let uri_to_id = self.uri_to_id.read().unwrap();
85        uri_to_id.get(uri).cloned()
86    }
87
88    fn exists(&self, uri: &str) -> bool {
89        self.uri_to_path(uri).map(|p| p.exists()).unwrap_or(false)
90    }
91
92    fn metadata(&self, uri: &str) -> Option<FileMetadata> {
93        let path = self.uri_to_path(uri)?;
94        let meta = fs::metadata(path).ok()?;
95
96        let file_type = if meta.is_file() {
97            FileType::File
98        }
99        else if meta.is_dir() {
100            FileType::Directory
101        }
102        else {
103            FileType::Other
104        };
105
106        let modified = meta.modified().ok().and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()).map(|d| d.as_secs());
107
108        Some(FileMetadata { file_type, len: meta.len(), modified })
109    }
110
111    fn read_dir(&self, uri: &str) -> Option<Vec<Arc<str>>> {
112        let path = self.uri_to_path(uri)?;
113        if !path.is_dir() {
114            return None;
115        }
116
117        let mut entries = Vec::new();
118        for entry in fs::read_dir(path).ok()? {
119            if let Ok(entry) = entry {
120                entries.push(self.path_to_uri(entry.path()));
121            }
122        }
123        Some(entries)
124    }
125}
126
127impl WritableVfs for DiskVfs {
128    fn write_file(&self, uri: &str, content: Arc<str>) {
129        if let Some(path) = self.uri_to_path(uri) {
130            if let Some(parent) = path.parent() {
131                let _ = fs::create_dir_all(parent);
132            }
133            let _ = fs::write(path, content.as_ref());
134        }
135    }
136
137    fn remove_file(&self, uri: &str) {
138        if let Some(path) = self.uri_to_path(uri) {
139            let _ = fs::remove_file(path);
140        }
141    }
142}