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#[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 pub fn new() -> Self {
35 Self::default()
36 }
37
38 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 Some(PathBuf::from(uri))
47 }
48
49 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}