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