makepad_file_server/
file_server.rs1use {
2 crate::{
3 makepad_file_protocol::{
4 DirectoryEntry,
5 FileNodeData,
6 FileTreeData,
7 FileError,
8 FileNotification,
9 FileRequest,
10 FileResponse,
11 },
12 },
13 std::{
14 cmp::Ordering,
15 fmt,
16 fs,
17 path::{Path, PathBuf},
18 sync::{Arc, RwLock},
19 },
20};
21
22pub struct FileServer {
23 next_connection_id: usize,
25 shared: Arc<RwLock<Shared >>,
27}
28
29impl FileServer {
30 pub fn new<P: Into<PathBuf >> (root_path: P) -> FileServer {
32 FileServer {
33 next_connection_id: 0,
34 shared: Arc::new(RwLock::new(Shared {
35 root_path: root_path.into(),
36 })),
37 }
38 }
39
40 pub fn connect(&mut self, notification_sender: Box<dyn NotificationSender>) -> FileServerConnection {
45 let connection_id = ConnectionId(self.next_connection_id);
46 self.next_connection_id += 1;
47 FileServerConnection {
48 _connection_id:connection_id,
49 shared: self.shared.clone(),
50 _notification_sender: notification_sender
51 }
52 }
53}
54
55pub struct FileServerConnection {
57 _connection_id: ConnectionId,
59 shared: Arc<RwLock<Shared >>,
61 _notification_sender: Box<dyn NotificationSender>,
63}
64
65impl FileServerConnection {
66 pub fn handle_request(&self, request: FileRequest) -> FileResponse {
71
72
73 match request {
74 FileRequest::LoadFileTree {with_data} => FileResponse::LoadFileTree(self.load_file_tree(with_data)),
75 FileRequest::OpenFile(path,id) => FileResponse::OpenFile(self.open_file(path, id)),
76 FileRequest::SaveFile(path, delta, id) => FileResponse::SaveFile(self.save_file(path, delta, id)),
77 }
78 }
79
80 fn load_file_tree(&self, with_data: bool) -> Result<FileTreeData, FileError> {
82 fn get_directory_entries(path: &Path, with_data: bool) -> Result<Vec<DirectoryEntry>, FileError> {
85 let mut entries = Vec::new();
86 for entry in fs::read_dir(path).map_err( | error | FileError::Unknown(error.to_string())) ? {
87 let entry = entry.map_err( | error | FileError::Unknown(error.to_string())) ?;
89 let entry_path = entry.path();
91 let name = entry.file_name();
93 if let Ok(name_string) = name.into_string() {
94 if entry_path.is_dir() && name_string == "target"
95 || name_string.starts_with('.') {
96 continue;
102 }
103 }
104 else {
105 continue;
107 }
108 entries.push(DirectoryEntry {
110 name: entry.file_name().to_string_lossy().to_string(),
111 node: if entry_path.is_dir() {
112 FileNodeData::Directory {
115 entries: get_directory_entries(&entry_path, with_data) ?,
116 }
117 } else if entry_path.is_file() {
118 if with_data {
119 let bytes: Vec<u8> = fs::read(&entry_path).map_err(
120 | error | FileError::Unknown(error.to_string())
121 ) ?;
122 FileNodeData::File {data: Some(bytes)}
123 }
124 else {
125 FileNodeData::File {data: None}
126 }
127 }
128 else {
129 continue
133 },
134 });
135 }
136
137 entries.sort_by( | entry_0, entry_1 | {
139 match &entry_0.node {
140 FileNodeData::Directory {..} => match &entry_1.node {
141 FileNodeData::Directory {..} => entry_0.name.cmp(&entry_1.name),
142 FileNodeData::File {..} => Ordering::Less
143 }
144 FileNodeData::File {..} => match &entry_1.node {
145 FileNodeData::Directory {..} => Ordering::Greater,
146 FileNodeData::File {..} => entry_0.name.cmp(&entry_1.name)
147 }
148 }
149 });
150 Ok(entries)
151 }
152
153 let root_path = self.shared.read().unwrap().root_path.clone();
154
155 let root = FileNodeData::Directory {
156 entries: get_directory_entries(&root_path, with_data) ?,
157 };
158 Ok(FileTreeData {root_path: "".into(), root})
159 }
160
161 fn make_full_path(&self, child_path:&String)->PathBuf{
162 let mut path = self.shared.read().unwrap().root_path.clone();
163 path.push(child_path);
164 path
165 }
166
167 fn open_file(&self, child_path: String, id:u64) -> Result<(String, String, u64), FileError> {
169 let path = self.make_full_path(&child_path);
170
171 let bytes = fs::read(&path).map_err(
172 | error | FileError::Unknown(error.to_string())
173 ) ?;
174 let text = String::from_utf8_lossy(&bytes);
183 Ok((child_path, text.to_string(), id))
184 }
185
186 fn save_file(
188 &self,
189 child_path: String,
190 new_content: String,
191 id: u64
192 ) -> Result<(String, String, String, u64), FileError> {
193 let path = self.make_full_path(&child_path);
194
195 let old_content = String::from_utf8_lossy(&fs::read(&path).map_err(
196 | error | FileError::Unknown(error.to_string())
197 ) ?).to_string();
198
199 fs::write(&path, &new_content).map_err(
200 | error | FileError::Unknown(error.to_string())
201 ) ?;
202
203 Ok((child_path, old_content, new_content, id))
204 }
205}
206
207pub trait NotificationSender: Send {
209 fn box_clone(&self) -> Box<dyn NotificationSender>;
211
212 fn send_notification(&self, notification: FileNotification);
214}
215
216impl<F: Clone + Fn(FileNotification) + Send + 'static> NotificationSender for F {
217 fn box_clone(&self) -> Box<dyn NotificationSender> {
218 Box::new(self.clone())
219 }
220
221 fn send_notification(&self, notification: FileNotification) {
222 self (notification)
223 }
224}
225
226impl Clone for Box<dyn NotificationSender> {
227 fn clone(&self) -> Self {
228 self.box_clone()
229 }
230}
231
232impl fmt::Debug for dyn NotificationSender {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 write!(f, "NotificationSender")
235 }
236}
237
238#[derive(Debug)]
240struct Shared {
241 root_path: PathBuf,
242}
243
244#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
246struct ConnectionId(usize);
247