use async_trait::async_trait;
use endbasic_std::storage::{DiskSpace, Drive, DriveFactory, DriveFiles, Metadata};
use std::collections::{BTreeMap, HashMap};
use std::io;
use std::str;
pub struct DemosDrive {
demos: HashMap<&'static str, (Metadata, String)>,
}
fn process_demo(bytes: &[u8]) -> String {
let raw_content = str::from_utf8(bytes).expect("Malformed demo file");
if cfg!(target_os = "windows") {
raw_content.replace("\r\n", "\n")
} else {
raw_content.to_owned()
}
}
impl Default for DemosDrive {
fn default() -> Self {
let mut demos = HashMap::default();
{
let content = process_demo(include_bytes!("../examples/guess.bas"));
let metadata = Metadata {
date: time::OffsetDateTime::from_unix_timestamp(1608693152).unwrap(),
length: content.len() as u64,
};
demos.insert("GUESS.BAS", (metadata, content));
}
{
let content = process_demo(include_bytes!("../examples/gpio.bas"));
let metadata = Metadata {
date: time::OffsetDateTime::from_unix_timestamp(1613316558).unwrap(),
length: content.len() as u64,
};
demos.insert("GPIO.BAS", (metadata, content));
}
{
let content = process_demo(include_bytes!("../examples/hello.bas"));
let metadata = Metadata {
date: time::OffsetDateTime::from_unix_timestamp(1608646800).unwrap(),
length: content.len() as u64,
};
demos.insert("HELLO.BAS", (metadata, content));
}
{
let content = process_demo(include_bytes!("../examples/palette.bas"));
let metadata = Metadata {
date: time::OffsetDateTime::from_unix_timestamp(1671243940).unwrap(),
length: content.len() as u64,
};
demos.insert("PALETTE.BAS", (metadata, content));
}
{
let content = process_demo(include_bytes!("../examples/tour.bas"));
let metadata = Metadata {
date: time::OffsetDateTime::from_unix_timestamp(1608774770).unwrap(),
length: content.len() as u64,
};
demos.insert("TOUR.BAS", (metadata, content));
}
Self { demos }
}
}
#[async_trait(?Send)]
impl Drive for DemosDrive {
async fn delete(&mut self, _name: &str) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::PermissionDenied, "The demos drive is read-only"))
}
async fn enumerate(&self) -> io::Result<DriveFiles> {
let mut entries = BTreeMap::new();
let mut bytes = 0;
for (name, (metadata, content)) in self.demos.iter() {
entries.insert(name.to_string(), metadata.clone());
bytes += content.len();
}
let files = self.demos.len();
let disk_quota = if bytes <= std::u64::MAX as usize && files <= std::u64::MAX as usize {
Some(DiskSpace::new(bytes as u64, files as u64))
} else {
None
};
let disk_free = Some(DiskSpace::new(0, 0));
Ok(DriveFiles::new(entries, disk_quota, disk_free))
}
async fn get(&self, name: &str) -> io::Result<String> {
let uc_name = name.to_ascii_uppercase();
match self.demos.get(&uc_name.as_ref()) {
Some(value) => {
let (_metadata, content) = value;
Ok(content.to_string())
}
None => Err(io::Error::new(io::ErrorKind::NotFound, "Demo not found")),
}
}
async fn put(&mut self, _name: &str, _content: &str) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::PermissionDenied, "The demos drive is read-only"))
}
}
#[derive(Default)]
pub struct DemoDriveFactory {}
impl DriveFactory for DemoDriveFactory {
fn create(&self, target: &str) -> io::Result<Box<dyn Drive>> {
if target.is_empty() {
Ok(Box::from(DemosDrive::default()))
} else {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Cannot specify a path to mount a demos drive",
))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_lite::future::block_on;
#[test]
fn test_demos_drive_delete() {
let mut drive = DemosDrive::default();
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.delete("hello.bas")).unwrap_err().kind()
);
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.delete("Hello.BAS")).unwrap_err().kind()
);
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.delete("unknown.bas")).unwrap_err().kind()
);
}
#[test]
fn test_demos_drive_enumerate() {
let drive = DemosDrive::default();
let files = block_on(drive.enumerate()).unwrap();
assert!(files.dirents().contains_key("GPIO.BAS"));
assert!(files.dirents().contains_key("GUESS.BAS"));
assert!(files.dirents().contains_key("HELLO.BAS"));
assert!(files.dirents().contains_key("PALETTE.BAS"));
assert!(files.dirents().contains_key("TOUR.BAS"));
assert!(files.disk_quota().unwrap().bytes() > 0);
assert_eq!(5, files.disk_quota().unwrap().files());
assert_eq!(DiskSpace::new(0, 0), files.disk_free().unwrap());
}
#[test]
fn test_demos_drive_get() {
let drive = DemosDrive::default();
assert_eq!(io::ErrorKind::NotFound, block_on(drive.get("unknown.bas")).unwrap_err().kind());
assert_eq!(
process_demo(include_bytes!("../examples/hello.bas")),
block_on(drive.get("hello.bas")).unwrap()
);
assert_eq!(
process_demo(include_bytes!("../examples/hello.bas")),
block_on(drive.get("Hello.Bas")).unwrap()
);
}
#[test]
fn test_demos_drive_put() {
let mut drive = DemosDrive::default();
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.put("hello.bas", "")).unwrap_err().kind()
);
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.put("Hello.BAS", "")).unwrap_err().kind()
);
assert_eq!(
io::ErrorKind::PermissionDenied,
block_on(drive.put("unknown.bas", "")).unwrap_err().kind()
);
}
#[test]
fn test_demos_drive_system_path() {
let drive = DemosDrive::default();
assert!(drive.system_path("foo").is_none());
}
}