1use async_trait::async_trait;
19use endbasic_std::storage::{DiskSpace, Drive, DriveFactory, DriveFiles, Metadata};
20use std::collections::{BTreeMap, HashMap};
21use std::io;
22use std::str;
23
24pub struct DemosDrive {
26 demos: HashMap<&'static str, (Metadata, String)>,
28}
29
30fn process_demo(bytes: &[u8]) -> String {
38 let raw_content = str::from_utf8(bytes).expect("Malformed demo file");
39 if cfg!(target_os = "windows") {
40 raw_content.replace("\r\n", "\n")
41 } else {
42 raw_content.to_owned()
43 }
44}
45
46impl Default for DemosDrive {
47 fn default() -> Self {
49 let mut demos = HashMap::default();
50 {
51 let content = process_demo(include_bytes!("../examples/fibonacci.bas"));
52 let metadata = Metadata {
53 date: time::OffsetDateTime::from_unix_timestamp(1719672741).unwrap(),
54 length: content.len() as u64,
55 };
56 demos.insert("FIBONACCI.BAS", (metadata, content));
57 }
58 {
59 let content = process_demo(include_bytes!("../examples/guess.bas"));
60 let metadata = Metadata {
61 date: time::OffsetDateTime::from_unix_timestamp(1608693152).unwrap(),
62 length: content.len() as u64,
63 };
64 demos.insert("GUESS.BAS", (metadata, content));
65 }
66 {
67 let content = process_demo(include_bytes!("../examples/gpio.bas"));
68 let metadata = Metadata {
69 date: time::OffsetDateTime::from_unix_timestamp(1613316558).unwrap(),
70 length: content.len() as u64,
71 };
72 demos.insert("GPIO.BAS", (metadata, content));
73 }
74 {
75 let content = process_demo(include_bytes!("../examples/hello.bas"));
76 let metadata = Metadata {
77 date: time::OffsetDateTime::from_unix_timestamp(1608646800).unwrap(),
78 length: content.len() as u64,
79 };
80 demos.insert("HELLO.BAS", (metadata, content));
81 }
82 {
83 let content = process_demo(include_bytes!("../examples/palette.bas"));
84 let metadata = Metadata {
85 date: time::OffsetDateTime::from_unix_timestamp(1671243940).unwrap(),
86 length: content.len() as u64,
87 };
88 demos.insert("PALETTE.BAS", (metadata, content));
89 }
90 {
91 let content = process_demo(include_bytes!("../examples/tour.bas"));
92 let metadata = Metadata {
93 date: time::OffsetDateTime::from_unix_timestamp(1608774770).unwrap(),
94 length: content.len() as u64,
95 };
96 demos.insert("TOUR.BAS", (metadata, content));
97 }
98 Self { demos }
99 }
100}
101
102#[async_trait(?Send)]
103impl Drive for DemosDrive {
104 async fn delete(&mut self, _name: &str) -> io::Result<()> {
105 Err(io::Error::new(io::ErrorKind::PermissionDenied, "The demos drive is read-only"))
106 }
107
108 async fn enumerate(&self) -> io::Result<DriveFiles> {
109 let mut entries = BTreeMap::new();
110 let mut bytes = 0;
111 for (name, (metadata, content)) in self.demos.iter() {
112 entries.insert(name.to_string(), metadata.clone());
113 bytes += content.len();
114 }
115 let files = self.demos.len();
116
117 let disk_quota = if bytes <= u64::MAX as usize && files <= u64::MAX as usize {
118 Some(DiskSpace::new(bytes as u64, files as u64))
119 } else {
120 None
122 };
123 let disk_free = Some(DiskSpace::new(0, 0));
124
125 Ok(DriveFiles::new(entries, disk_quota, disk_free))
126 }
127
128 async fn get(&self, name: &str) -> io::Result<String> {
129 let uc_name = name.to_ascii_uppercase();
130 match self.demos.get(&uc_name.as_ref()) {
131 Some(value) => {
132 let (_metadata, content) = value;
133 Ok(content.to_string())
134 }
135 None => Err(io::Error::new(io::ErrorKind::NotFound, "Demo not found")),
136 }
137 }
138
139 async fn put(&mut self, _name: &str, _content: &str) -> io::Result<()> {
140 Err(io::Error::new(io::ErrorKind::PermissionDenied, "The demos drive is read-only"))
141 }
142}
143
144#[derive(Default)]
146pub struct DemoDriveFactory {}
147
148impl DriveFactory for DemoDriveFactory {
149 fn create(&self, target: &str) -> io::Result<Box<dyn Drive>> {
150 if target.is_empty() {
151 Ok(Box::from(DemosDrive::default()))
152 } else {
153 Err(io::Error::new(
154 io::ErrorKind::InvalidInput,
155 "Cannot specify a path to mount a demos drive",
156 ))
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use futures_lite::future::block_on;
165
166 #[test]
167 fn test_demos_drive_delete() {
168 let mut drive = DemosDrive::default();
169
170 assert_eq!(
171 io::ErrorKind::PermissionDenied,
172 block_on(drive.delete("hello.bas")).unwrap_err().kind()
173 );
174 assert_eq!(
175 io::ErrorKind::PermissionDenied,
176 block_on(drive.delete("Hello.BAS")).unwrap_err().kind()
177 );
178
179 assert_eq!(
180 io::ErrorKind::PermissionDenied,
181 block_on(drive.delete("unknown.bas")).unwrap_err().kind()
182 );
183 }
184
185 #[test]
186 fn test_demos_drive_enumerate() {
187 let drive = DemosDrive::default();
188
189 let files = block_on(drive.enumerate()).unwrap();
190 assert!(files.dirents().contains_key("FIBONACCI.BAS"));
191 assert!(files.dirents().contains_key("GPIO.BAS"));
192 assert!(files.dirents().contains_key("GUESS.BAS"));
193 assert!(files.dirents().contains_key("HELLO.BAS"));
194 assert!(files.dirents().contains_key("PALETTE.BAS"));
195 assert!(files.dirents().contains_key("TOUR.BAS"));
196
197 assert!(files.disk_quota().unwrap().bytes() > 0);
198 assert_eq!(6, files.disk_quota().unwrap().files());
199 assert_eq!(DiskSpace::new(0, 0), files.disk_free().unwrap());
200 }
201
202 #[test]
203 fn test_demos_drive_get() {
204 let drive = DemosDrive::default();
205
206 assert_eq!(io::ErrorKind::NotFound, block_on(drive.get("unknown.bas")).unwrap_err().kind());
207
208 assert_eq!(
209 process_demo(include_bytes!("../examples/hello.bas")),
210 block_on(drive.get("hello.bas")).unwrap()
211 );
212 assert_eq!(
213 process_demo(include_bytes!("../examples/hello.bas")),
214 block_on(drive.get("Hello.Bas")).unwrap()
215 );
216 }
217
218 #[test]
219 fn test_demos_drive_put() {
220 let mut drive = DemosDrive::default();
221
222 assert_eq!(
223 io::ErrorKind::PermissionDenied,
224 block_on(drive.put("hello.bas", "")).unwrap_err().kind()
225 );
226 assert_eq!(
227 io::ErrorKind::PermissionDenied,
228 block_on(drive.put("Hello.BAS", "")).unwrap_err().kind()
229 );
230
231 assert_eq!(
232 io::ErrorKind::PermissionDenied,
233 block_on(drive.put("unknown.bas", "")).unwrap_err().kind()
234 );
235 }
236
237 #[test]
238 fn test_demos_drive_system_path() {
239 let drive = DemosDrive::default();
240 assert!(drive.system_path("foo").is_none());
241 }
242}