endbasic_std/storage/
mem.rs

1// EndBASIC
2// Copyright 2021 Julio Merino
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License.  You may obtain a copy
6// of the License at:
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
13// License for the specific language governing permissions and limitations
14// under the License.
15
16//! In-memory implementation of the storage system.
17
18use crate::storage::{DiskSpace, Drive, DriveFactory, DriveFiles, FileAcls, Metadata};
19use async_trait::async_trait;
20use std::collections::{BTreeMap, HashMap, HashSet};
21use std::io;
22use std::str;
23
24/// A drive that records all data in memory only.
25#[derive(Default)]
26pub struct InMemoryDrive {
27    programs: HashMap<String, (String, HashSet<String>)>,
28
29    // TODO(jmmv): These fields are currently exposed only to allow testing for the consumers of
30    // these details and are not enforced in the drive.  It might be nice to actually implement
31    // proper support for this.
32    pub(crate) fake_disk_quota: Option<DiskSpace>,
33    pub(crate) fake_disk_free: Option<DiskSpace>,
34}
35
36#[async_trait(?Send)]
37impl Drive for InMemoryDrive {
38    async fn delete(&mut self, name: &str) -> io::Result<()> {
39        match self.programs.remove(name) {
40            Some(_) => Ok(()),
41            None => Err(io::Error::new(io::ErrorKind::NotFound, "Entry not found")),
42        }
43    }
44
45    async fn enumerate(&self) -> io::Result<DriveFiles> {
46        let date = time::OffsetDateTime::from_unix_timestamp(1_588_757_875).unwrap();
47
48        let mut entries = BTreeMap::new();
49        for (name, (contents, _readers)) in &self.programs {
50            entries.insert(name.clone(), Metadata { date, length: contents.len() as u64 });
51        }
52        Ok(DriveFiles::new(entries, self.fake_disk_quota, self.fake_disk_free))
53    }
54
55    async fn get(&self, name: &str) -> io::Result<String> {
56        match self.programs.get(name) {
57            Some((content, _readers)) => Ok(content.to_owned()),
58            None => Err(io::Error::new(io::ErrorKind::NotFound, "Entry not found")),
59        }
60    }
61
62    async fn get_acls(&self, name: &str) -> io::Result<FileAcls> {
63        match self.programs.get(name) {
64            Some((_content, readers)) => {
65                let mut readers = readers.iter().map(String::to_owned).collect::<Vec<String>>();
66                // There is no need to sort the returned ACLs, but doing so simplifies testing...
67                // and this in-memory drive exists mostly for testing only.
68                readers.sort();
69                Ok(FileAcls::default().with_readers(readers))
70            }
71            None => Err(io::Error::new(io::ErrorKind::NotFound, "Entry not found")),
72        }
73    }
74
75    async fn put(&mut self, name: &str, content: &str) -> io::Result<()> {
76        if let Some((prev_content, _readers)) = self.programs.get_mut(name) {
77            content.clone_into(prev_content);
78            return Ok(());
79        };
80        self.programs.insert(name.to_owned(), (content.to_owned(), HashSet::new()));
81        Ok(())
82    }
83
84    async fn update_acls(
85        &mut self,
86        name: &str,
87        add: &FileAcls,
88        remove: &FileAcls,
89    ) -> io::Result<()> {
90        let readers = match self.programs.get_mut(name) {
91            Some((_content, readers)) => readers,
92            None => return Err(io::Error::new(io::ErrorKind::NotFound, "Entry not found")),
93        };
94        for reader in remove.readers() {
95            readers.remove(reader);
96        }
97        for reader in add.readers() {
98            readers.insert(reader.to_owned());
99        }
100        Ok(())
101    }
102}
103
104/// Factory for in-memory drives.
105#[derive(Default)]
106pub struct InMemoryDriveFactory {}
107
108impl DriveFactory for InMemoryDriveFactory {
109    fn create(&self, target: &str) -> io::Result<Box<dyn Drive>> {
110        if target.is_empty() {
111            Ok(Box::from(InMemoryDrive::default()))
112        } else {
113            Err(io::Error::new(
114                io::ErrorKind::InvalidInput,
115                "Cannot specify a path to mount an in-memory drive",
116            ))
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    /// Convenience function to instantiate a `FileAcls` with the `r` readers.
126    fn readers(r: &[&str]) -> FileAcls {
127        FileAcls::default().with_readers(r.iter().map(|x| x.to_string()).collect::<Vec<String>>())
128    }
129
130    #[tokio::test]
131    async fn test_inmemorydrive_put_respects_acls() {
132        let mut drive = InMemoryDrive::default();
133        drive.put("untouched", "some content").await.unwrap();
134
135        drive.put("file", "before").await.unwrap();
136        drive.update_acls("file", &readers(&["r1"]), &FileAcls::default()).await.unwrap();
137
138        assert_eq!("before", drive.get("file").await.unwrap());
139        assert_eq!(readers(&["r1"]), drive.get_acls("file").await.unwrap());
140
141        drive.put("file", "after").await.unwrap();
142
143        assert_eq!("after", drive.get("file").await.unwrap());
144        assert_eq!(readers(&["r1"]), drive.get_acls("file").await.unwrap());
145    }
146
147    #[tokio::test]
148    async fn test_inmemorydrive_get_update_acls() {
149        let mut drive = InMemoryDrive::default();
150        drive.put("untouched", "some content").await.unwrap();
151
152        let err =
153            drive.update_acls("file", &readers(&["r0"]), &FileAcls::default()).await.unwrap_err();
154        assert_eq!(io::ErrorKind::NotFound, err.kind());
155
156        drive.put("file", "some content").await.unwrap();
157        assert_eq!(FileAcls::default(), drive.get_acls("file").await.unwrap());
158
159        // Add some new readers and try to remove a non-existing one.
160        drive.update_acls("file", &readers(&["r1", "r2"]), &readers(&["r3"])).await.unwrap();
161        assert_eq!(readers(&["r1", "r2"]), drive.get_acls("file").await.unwrap());
162
163        // Add a new reader and a duplicate and remove an existing one.
164        drive.update_acls("file", &readers(&["r2", "r2", "r3"]), &readers(&["r1"])).await.unwrap();
165        assert_eq!(readers(&["r2", "r3"]), drive.get_acls("file").await.unwrap());
166
167        // Make sure other files were not affected.
168        assert_eq!(FileAcls::default(), drive.get_acls("untouched").await.unwrap());
169    }
170
171    #[test]
172    fn test_inmemorydrive_system_path() {
173        let drive = InMemoryDrive::default();
174        assert!(drive.system_path("foo").is_none());
175    }
176}