endbasic_std/storage/
mod.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//! Storage-related abstractions and commands.
17
18use async_trait::async_trait;
19use std::collections::{BTreeMap, HashMap};
20use std::fmt::{self};
21use std::io;
22use std::path::PathBuf;
23use std::str;
24use time::error::Format;
25
26mod cmds;
27pub use cmds::*;
28mod fs;
29pub use fs::*;
30mod mem;
31pub use mem::*;
32
33/// Converts a time formatting error to an I/O error.
34pub(crate) fn time_format_error_to_io_error(e: Format) -> io::Error {
35    match e {
36        Format::StdIo(e) => e,
37        e => io::Error::new(io::ErrorKind::Other, format!("{}", e)),
38    }
39}
40
41/// Metadata of an entry in a storage medium.
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct Metadata {
44    /// Last modification time of the entry.
45    pub date: time::OffsetDateTime,
46
47    /// Total size of the entry.
48    pub length: u64,
49}
50
51/// Describes the ACLs of a file.
52#[derive(Clone, Debug, Default, Eq, PartialEq)]
53pub struct FileAcls {
54    /// List of principals that are allowed to read the file.
55    pub readers: Vec<String>,
56}
57
58impl FileAcls {
59    /// Returns true if this group of ACLs is empty.
60    pub fn is_empty(&self) -> bool {
61        self.readers.is_empty()
62    }
63
64    /// Extends this set of ACLs with the given `readers`.
65    pub fn with_readers<T: Into<Vec<String>>>(mut self, readers: T) -> Self {
66        self.readers.extend(readers.into());
67        self
68    }
69
70    /// Gets the list of principals that are allowed to read the file.
71    pub fn readers(&self) -> &[String] {
72        &self.readers
73    }
74
75    /// Modifies the readers list by appending `reader` to it.
76    pub fn add_reader<R: Into<String>>(&mut self, reader: R) {
77        self.readers.push(reader.into());
78    }
79}
80
81/// Representation of some amount of disk space.  Can be used to express both quotas and usage.
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub struct DiskSpace {
84    /// Number of bytes used or allowed.
85    pub bytes: u64,
86
87    /// Number of files used or allowed.
88    pub files: u64,
89}
90
91impl DiskSpace {
92    /// Creates a new representation of disk space.
93    pub fn new(bytes: u64, files: u64) -> Self {
94        Self { bytes, files }
95    }
96
97    /// Returns the amount of bytes in this disk space.
98    pub fn bytes(&self) -> u64 {
99        self.bytes
100    }
101
102    /// Returns the number of files in this disk space.
103    pub fn files(&self) -> u64 {
104        self.files
105    }
106}
107
108/// Collection of entries in the store and their metadata.  Used to represent the result of the
109/// `Drive::enumerate` call.
110#[derive(Debug)]
111pub struct DriveFiles {
112    dirents: BTreeMap<String, Metadata>,
113    disk_quota: Option<DiskSpace>,
114    disk_free: Option<DiskSpace>,
115}
116
117impl DriveFiles {
118    /// Creates a new collection of files with the given `dirents`.
119    pub fn new(
120        dirents: BTreeMap<String, Metadata>,
121        disk_quota: Option<DiskSpace>,
122        disk_free: Option<DiskSpace>,
123    ) -> Self {
124        Self { dirents, disk_quota, disk_free }
125    }
126
127    /// Returns the collection of files in this result.
128    pub fn dirents(&self) -> &BTreeMap<String, Metadata> {
129        &self.dirents
130    }
131
132    /// Returns the user's disk quota, if known.
133    pub fn disk_quota(&self) -> &Option<DiskSpace> {
134        &self.disk_quota
135    }
136
137    /// Returns the disk free space, if known.
138    pub fn disk_free(&self) -> &Option<DiskSpace> {
139        &self.disk_free
140    }
141}
142
143/// Abstract operations to load and store programs on some storage medium.
144#[async_trait(?Send)]
145pub trait Drive {
146    /// Deletes the program given by `name`.
147    async fn delete(&mut self, name: &str) -> io::Result<()>;
148
149    /// Returns the entries in the store and their metadata.
150    async fn enumerate(&self) -> io::Result<DriveFiles>;
151
152    /// Loads the contents of the program given by `name`.
153    async fn get(&self, name: &str) -> io::Result<String>;
154
155    /// Gets the ACLs of the file `_name`.
156    async fn get_acls(&self, _name: &str) -> io::Result<FileAcls> {
157        Err(io::Error::new(io::ErrorKind::Other, "Operation not supported by drive"))
158    }
159
160    /// Saves the in-memory program given by `content` into `name`.
161    async fn put(&mut self, name: &str, content: &str) -> io::Result<()>;
162
163    /// Updates the ACLs of the file `_name` by extending them with the contents of `_add` and
164    /// removing the existing entries listed in `_remove`.
165    async fn update_acls(
166        &mut self,
167        _name: &str,
168        _add: &FileAcls,
169        _remove: &FileAcls,
170    ) -> io::Result<()> {
171        Err(io::Error::new(io::ErrorKind::Other, "Operation not supported by drive"))
172    }
173
174    /// Gets the system-addressable path of the file `_name`, if any.
175    fn system_path(&self, _name: &str) -> Option<PathBuf> {
176        None
177    }
178}
179
180/// Unique identifier for a drive.
181///
182/// The name contained in the key is stored in its canonical form.
183#[derive(Clone, Debug, Eq, Hash, PartialEq)]
184struct DriveKey(String);
185
186impl DriveKey {
187    /// Constructs a drive from a raw string, validating that the name is valid.
188    fn new<T: Into<String>>(drive: T) -> io::Result<DriveKey> {
189        let drive = drive.into();
190        if !DriveKey::is_valid(&drive) {
191            return Err(io::Error::new(
192                io::ErrorKind::InvalidInput,
193                format!("Invalid drive name '{}'", drive),
194            ));
195        }
196        Ok(DriveKey(drive.to_uppercase()))
197    }
198
199    /// Returns true if the given drive name is valid.
200    fn is_valid(s: &str) -> bool {
201        !s.is_empty() && !s.chars().any(|c| c == ':' || c == '\\' || c == '/' || c == '.')
202    }
203}
204
205impl fmt::Display for DriveKey {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        self.0.fmt(f)
208    }
209}
210
211/// Representation of an EndBASIC path.
212///
213/// This implementation is not as efficient as it could be, given that many times we don't have to
214/// clone the input string to break it into pieces.  However, owning the components here makes the
215/// implementation much simpler and, for now, should not be a noticeable performance problem.
216#[derive(Debug)]
217struct Location {
218    drive: Option<DriveKey>,
219    path: String,
220}
221
222impl Location {
223    /// Constructs a new path from the contents of `s` after validating it.
224    fn new(s: &str) -> io::Result<Self> {
225        let fields = s.split(':').collect::<Vec<&str>>();
226        let (drive, mut path) = match fields.as_slice() {
227            [drive, path] => (Some(DriveKey::new(*drive)?), *path),
228            [path] => (None, *path),
229            _ => {
230                return Err(io::Error::new(
231                    io::ErrorKind::InvalidInput,
232                    format!("Too many : separators in path '{}'", s),
233                ))
234            }
235        };
236
237        if path.is_empty() {
238            path = "/";
239        } else {
240            if !Location::is_path_valid(path)
241                || path == "."
242                || path == ".."
243                || path == "/."
244                || path == "/.."
245            {
246                return Err(io::Error::new(
247                    io::ErrorKind::InvalidInput,
248                    format!("Invalid path '{}'", s),
249                ));
250            }
251            let slashes = path.chars().fold(0, |a, c| if c == '/' { a + 1 } else { a });
252            if (slashes == 1 && !path.starts_with('/')) || slashes > 1 {
253                return Err(io::Error::new(
254                    io::ErrorKind::InvalidInput,
255                    format!("Too many / separators in path '{}'", s),
256                ));
257            }
258        }
259
260        Ok(Self { drive, path: path.to_owned() })
261    }
262
263    /// Convenience function to create a path to the root of a `drive`.
264    fn with_drive_root(drive: DriveKey) -> Self {
265        Self { drive: Some(drive), path: "/".to_owned() }
266    }
267
268    /// Returns true if the given path is valid.
269    fn is_path_valid(s: &str) -> bool {
270        !s.is_empty() && !s.chars().any(|c| c == ':' || c == '\\')
271    }
272
273    /// Returns the last component of this path, or none if there is no referenced file.
274    fn leaf_name(&self) -> Option<&str> {
275        if self.path == "/" {
276            None
277        } else if self.path.starts_with('/') {
278            Some(&self.path[1..])
279        } else {
280            Some(&self.path)
281        }
282    }
283}
284
285impl fmt::Display for Location {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match &self.drive {
288            Some(drive) => write!(f, "{}:{}", drive.0, self.path),
289            None => self.path.fmt(f),
290        }
291    }
292}
293
294/// Trait to instantiate drives of a given type.
295pub trait DriveFactory {
296    /// Creates a new drive for `target`.
297    fn create(&self, target: &str) -> io::Result<Box<dyn Drive>>;
298}
299
300/// Given a mount URI, validates it and returns the `(scheme, path)` pair.
301fn split_uri(uri: &str) -> io::Result<(&str, &str)> {
302    match uri.find("://") {
303        Some(pos) if pos > 0 => Ok((&uri[0..pos], &uri[pos + 3..])),
304        _ => Err(io::Error::new(
305            io::ErrorKind::InvalidInput,
306            "Mount URI must be of the form scheme://path",
307        )),
308    }
309}
310
311/// Metadata for a mounted drive.
312struct MountedDrive {
313    uri: String,
314    drive: Box<dyn Drive>,
315}
316
317/// Storage subsystem representation.
318///
319/// At the moment, the storage subsystem is backed by a single drive, so this type is a wrapper
320/// for the `Drive` type.
321pub struct Storage {
322    /// Mapping of target scheme names to drive factories.
323    factories: HashMap<String, Box<dyn DriveFactory>>,
324
325    /// Mapping of drive names to drives.
326    drives: HashMap<DriveKey, MountedDrive>,
327
328    /// Name of the active drive, which must be present in `drives`.
329    current: DriveKey,
330}
331
332impl Default for Storage {
333    /// Creates a new storage subsytem backed by an in-memory drive.
334    fn default() -> Self {
335        let mut factories: HashMap<String, Box<dyn DriveFactory>> = HashMap::default();
336        factories.insert("memory".to_owned(), Box::from(InMemoryDriveFactory::default()));
337
338        let drive: Box<dyn Drive> = Box::from(InMemoryDrive::default());
339
340        let mut drives = HashMap::new();
341        let key = DriveKey::new("MEMORY").expect("Hardcoded drive name must be valid");
342        let mounted_drive = MountedDrive { uri: "memory://".to_owned(), drive };
343        drives.insert(key.clone(), mounted_drive);
344        Self { factories, drives, current: key }
345    }
346}
347
348impl Storage {
349    /// Registers a new drive `factory` to handle the `scheme`.  Must not have previously been
350    /// registered and the `scheme` must be in lowercase.
351    pub fn register_scheme(&mut self, scheme: &str, factory: Box<dyn DriveFactory>) {
352        assert_eq!(scheme.to_lowercase(), scheme);
353        let previous = self.factories.insert(scheme.to_owned(), factory);
354        assert!(previous.is_none(), "Tried to register {} twice", scheme);
355    }
356
357    /// Returns true if the `scheme` is already registered.
358    pub fn has_scheme(&self, scheme: &str) -> bool {
359        self.factories.contains_key(scheme)
360    }
361
362    /// Converts a location, which needn't exist, to its canonical form.
363    pub fn make_canonical(&self, raw_location: &str) -> io::Result<String> {
364        let mut location = Location::new(raw_location)?;
365        if location.drive.is_none() {
366            location.drive = Some(self.current.clone());
367        }
368        Ok(location.to_string())
369    }
370
371    /// Attaches a new `drive` with `name`, which was instantiated with `uri`.
372    ///
373    /// The `name` must be valid and must not yet have been registered.
374    fn attach(&mut self, name: &str, uri: &str, drive: Box<dyn Drive>) -> io::Result<()> {
375        let key = DriveKey::new(name)?;
376        if self.drives.contains_key(&key) {
377            return Err(io::Error::new(
378                io::ErrorKind::AlreadyExists,
379                format!("Drive '{}' is already mounted", name),
380            ));
381        }
382        let mounted_drive = MountedDrive { uri: uri.to_owned(), drive };
383        self.drives.insert(DriveKey::new(name)?, mounted_drive);
384        Ok(())
385    }
386
387    /// Instantiates and attaches a new `drive` with `name` that points to `uri`.
388    ///
389    /// The `name` must be valid and must not yet have been registered.
390    pub fn mount(&mut self, name: &str, uri: &str) -> io::Result<()> {
391        let (scheme, path) = split_uri(uri)?;
392        let drive = match self.factories.get(&scheme.to_lowercase()) {
393            Some(factory) => factory.create(path)?,
394            None => {
395                return Err(io::Error::new(
396                    io::ErrorKind::InvalidInput,
397                    format!("Unknown mount scheme '{}'", scheme),
398                ))
399            }
400        };
401        self.attach(name, uri, drive)
402    }
403
404    /// Detaches an existing drive named `name`.
405    ///
406    /// The drive `name` must exist, cannot be the current drive, and cannot be the last mounted
407    /// drive.
408    pub fn unmount(&mut self, name: &str) -> io::Result<()> {
409        let key = DriveKey::new(name)?;
410        if !self.drives.contains_key(&key) {
411            return Err(io::Error::new(
412                io::ErrorKind::NotFound,
413                format!("Drive '{}' is not mounted", name),
414            ));
415        }
416        if self.current == key {
417            return Err(io::Error::new(
418                io::ErrorKind::AlreadyExists,
419                format!("Cannot unmount the current drive '{}'", name),
420            ));
421        }
422        assert!(
423            self.drives.len() > 1,
424            "There must be more than one drive if the current drive is not the given name"
425        );
426        self.drives.remove(&key).expect("Drive presence in map checked above");
427        Ok(())
428    }
429
430    /// Returns information about the mounted drives as a mapping of drive names to the URIs that
431    /// were used to mount them.
432    pub fn mounted(&self) -> BTreeMap<&str, &str> {
433        let mut info = BTreeMap::new();
434        for (name, mounted_drive) in &self.drives {
435            info.insert(name.0.as_str(), mounted_drive.uri.as_str());
436        }
437        info
438    }
439
440    /// Changes the current location.
441    ///
442    /// Given that we currently do not support directories, the location can only be of the forms
443    /// `DRIVE:` or `DRIVE:/`.
444    pub fn cd(&mut self, location: &str) -> io::Result<()> {
445        let location = Location::new(location)?;
446        if location.leaf_name().is_some() {
447            return Err(io::Error::new(io::ErrorKind::InvalidInput, "Cannot cd to a file"));
448        }
449
450        match location.drive {
451            Some(drive) => {
452                if !self.drives.contains_key(&drive) {
453                    return Err(io::Error::new(
454                        io::ErrorKind::AlreadyExists,
455                        format!("Drive '{}' is not mounted", drive),
456                    ));
457                }
458                self.current = drive;
459                Ok(())
460            }
461            None => Ok(()),
462        }
463    }
464
465    /// Returns the current location, used to resolve relative paths.
466    pub fn cwd(&self) -> String {
467        Location::with_drive_root(self.current.clone()).to_string()
468    }
469
470    /// Returns the drive referenced by `location`, or an error if it doesn't exist.
471    fn get_drive(&self, location: &Location) -> io::Result<&dyn Drive> {
472        match &location.drive {
473            Some(key) => match self.drives.get(key) {
474                Some(mounted_drive) => Ok(mounted_drive.drive.as_ref()),
475                None => Err(io::Error::new(
476                    io::ErrorKind::NotFound,
477                    format!("Drive '{}' is not mounted", key),
478                )),
479            },
480            None => Ok(self
481                .drives
482                .get(&self.current)
483                .expect("Current drive out of sync")
484                .drive
485                .as_ref()),
486        }
487    }
488
489    /// Returns the drive referenced by `location`, or an error if it doesn't exist.
490    fn get_drive_mut(&mut self, location: &Location) -> io::Result<&mut dyn Drive> {
491        match &location.drive {
492            Some(key) => match self.drives.get_mut(key) {
493                Some(mounted_drive) => Ok(mounted_drive.drive.as_mut()),
494                None => Err(io::Error::new(
495                    io::ErrorKind::NotFound,
496                    format!("Drive '{}' is not mounted", key),
497                )),
498            },
499            None => Ok(self
500                .drives
501                .get_mut(&self.current)
502                .expect("Current drive out of sync")
503                .drive
504                .as_mut()),
505        }
506    }
507
508    /// Deletes the program given by `raw_location`.
509    pub async fn delete(&mut self, raw_location: &str) -> io::Result<()> {
510        let location = Location::new(raw_location)?;
511        match location.leaf_name() {
512            Some(name) => self.get_drive_mut(&location)?.delete(name).await,
513            None => Err(io::Error::new(
514                io::ErrorKind::NotFound,
515                format!("Missing file name in path '{}'", raw_location),
516            )),
517        }
518    }
519
520    /// Returns a sorted list of the entries in `raw_location` and their metadata.
521    pub async fn enumerate(&self, raw_location: &str) -> io::Result<DriveFiles> {
522        let location = Location::new(raw_location)?;
523        match location.leaf_name() {
524            Some(_) => Err(io::Error::new(
525                io::ErrorKind::NotFound,
526                format!("Location '{}' is not a directory", raw_location),
527            )),
528            None => self.get_drive(&location)?.enumerate().await,
529        }
530    }
531
532    /// Loads the contents of the program given by `raw_location`.
533    pub async fn get(&self, raw_location: &str) -> io::Result<String> {
534        let location = Location::new(raw_location)?;
535        match location.leaf_name() {
536            Some(name) => self.get_drive(&location)?.get(name).await,
537            None => Err(io::Error::new(
538                io::ErrorKind::NotFound,
539                format!("Missing file name in path '{}'", raw_location),
540            )),
541        }
542    }
543
544    /// Gets the ACLs of the file `raw_location`.
545    pub async fn get_acls(&self, raw_location: &str) -> io::Result<FileAcls> {
546        let location = Location::new(raw_location)?;
547        match location.leaf_name() {
548            Some(name) => self.get_drive(&location)?.get_acls(name).await,
549            None => Err(io::Error::new(
550                io::ErrorKind::NotFound,
551                format!("Missing file name in path '{}'", raw_location),
552            )),
553        }
554    }
555
556    /// Saves the in-memory program given by `content` into `raw_location`.
557    pub async fn put(&mut self, raw_location: &str, content: &str) -> io::Result<()> {
558        let location = Location::new(raw_location)?;
559        match location.leaf_name() {
560            Some(name) => self.get_drive_mut(&location)?.put(name, content).await,
561            None => Err(io::Error::new(
562                io::ErrorKind::NotFound,
563                format!("Missing file name in path '{}'", raw_location),
564            )),
565        }
566    }
567
568    /// Updates the ACLs of the file `raw_location` by extending them with the contents of `add` and
569    /// removing the existing entries listed in `remove`.
570    pub async fn update_acls(
571        &mut self,
572        raw_location: &str,
573        add: &FileAcls,
574        remove: &FileAcls,
575    ) -> io::Result<()> {
576        let location = Location::new(raw_location)?;
577        match location.leaf_name() {
578            Some(name) => self.get_drive_mut(&location)?.update_acls(name, add, remove).await,
579            None => Err(io::Error::new(
580                io::ErrorKind::NotFound,
581                format!("Missing file name in path '{}'", raw_location),
582            )),
583        }
584    }
585
586    /// Gets the system-addressable path of `raw_location`, if any.
587    pub fn system_path(&self, raw_location: &str) -> io::Result<Option<PathBuf>> {
588        let location = Location::new(raw_location)?;
589        match location.leaf_name() {
590            Some(name) => Ok(self.get_drive(&location)?.system_path(name)),
591            None => Ok(self.get_drive(&location)?.system_path("")),
592        }
593    }
594}
595
596#[cfg(test)]
597mod tests {
598    use super::*;
599    use futures_lite::future::block_on;
600
601    #[test]
602    fn test_split_uri_ok() {
603        assert_eq!(("the-scheme", ""), split_uri("the-scheme://").unwrap());
604        assert_eq!(("foo", "/some:/target"), split_uri("foo:///some:/target").unwrap());
605        assert_eq!(("foo", "bar://baz"), split_uri("foo://bar://baz").unwrap());
606    }
607
608    #[test]
609    fn test_split_uri_errors() {
610        for uri in &["", "://abc", "foo:/", "bar//", "/baz/"] {
611            assert_eq!(
612                "Mount URI must be of the form scheme://path",
613                format!("{}", split_uri(uri).unwrap_err())
614            );
615        }
616    }
617
618    #[test]
619    fn test_drivekey_new() {
620        assert_eq!(DriveKey("FOO".to_owned()), DriveKey::new("foo").unwrap());
621        assert_eq!(DriveKey("A".to_owned()), DriveKey::new("A".to_owned()).unwrap());
622    }
623
624    #[test]
625    fn test_drivekey_errors() {
626        assert_eq!("Invalid drive name ''", format!("{}", DriveKey::new("").unwrap_err()));
627        assert_eq!("Invalid drive name 'a:b'", format!("{}", DriveKey::new("a:b").unwrap_err()));
628        assert_eq!("Invalid drive name 'a\\b'", format!("{}", DriveKey::new("a\\b").unwrap_err()));
629        assert_eq!("Invalid drive name 'a/b'", format!("{}", DriveKey::new("a/b").unwrap_err()));
630        assert_eq!("Invalid drive name 'a.b'", format!("{}", DriveKey::new("a.b").unwrap_err()));
631    }
632
633    #[test]
634    fn test_location_new_ok() {
635        fn check(exp_drive: Option<&str>, exp_path: &str, input: &str) {
636            let rp = Location::new(input).unwrap();
637            assert_eq!(exp_drive.map(|s| DriveKey::new(s).unwrap()), rp.drive);
638            assert_eq!(exp_path, rp.path);
639        }
640
641        check(None, "/", "/");
642        check(None, "/foo.bas", "/foo.bas");
643
644        check(Some("A"), "/", "a:");
645        check(Some("ABC"), "/foo.bas", "abc:/foo.bas");
646        check(Some("ABC"), "Foo.Bas", "abc:Foo.Bas");
647    }
648
649    #[test]
650    fn test_location_new_errors() {
651        fn check(exp_error: &str, input: &str) {
652            let e = Location::new(input).unwrap_err();
653            assert_eq!(io::ErrorKind::InvalidInput, e.kind());
654            assert_eq!(exp_error, format!("{}", e));
655        }
656
657        check("Too many : separators in path 'a:b:c'", "a:b:c");
658
659        check("Invalid drive name ''", ":");
660        check("Invalid drive name 'a\\b'", "a\\b:");
661        check("Invalid drive name 'a/b'", "a/b:");
662        check("Invalid drive name 'a.b'", "a.b:");
663
664        check("Invalid path 'a:\\'", "a:\\");
665        check("Invalid path 'a:.'", "a:.");
666        check("Invalid path 'a:..'", "a:..");
667        check("Invalid path 'a:/.'", "a:/.");
668        check("Invalid path 'a:/..'", "a:/..");
669        check("Invalid path '.'", ".");
670        check("Invalid path '..'", "..");
671        check("Invalid path '/.'", "/.");
672        check("Invalid path '/..'", "/..");
673
674        check("Too many / separators in path 'a://.'", "a://.");
675        check("Too many / separators in path 'a:../'", "a:../");
676        check("Too many / separators in path 'a:b/c'", "a:b/c");
677        check("Too many / separators in path 'a:/b/c'", "a:/b/c");
678    }
679
680    #[test]
681    fn test_location_leaf_name() {
682        assert_eq!(None, Location::new("drv:/").unwrap().leaf_name());
683        assert_eq!(None, Location::new("drv:").unwrap().leaf_name());
684
685        assert_eq!(Some("abc.txt"), Location::new("a:/abc.txt").unwrap().leaf_name());
686        assert_eq!(Some("abc.txt"), Location::new("a:abc.txt").unwrap().leaf_name());
687        assert_eq!(Some("abc.txt"), Location::new("abc.txt").unwrap().leaf_name());
688    }
689
690    /// Convenience helper to obtain the sorted list of mounted drive names in canonical form.
691    fn drive_names(storage: &Storage) -> Vec<String> {
692        let mut names = storage.drives.keys().map(DriveKey::to_string).collect::<Vec<String>>();
693        names.sort();
694        names
695    }
696
697    #[test]
698    fn test_storage_default() {
699        let storage = Storage::default();
700        assert_eq!("MEMORY:/", storage.cwd());
701    }
702
703    #[test]
704    fn test_storage_make_canonical_ok() {
705        let mut storage = Storage::default();
706        storage.mount("some", "memory://").unwrap();
707
708        assert_eq!("MEMORY:foo.bar", storage.make_canonical("foo.bar").unwrap());
709        assert_eq!("MEMORY:/foo.bar", storage.make_canonical("/foo.bar").unwrap());
710        storage.cd("some:/").unwrap();
711        assert_eq!("SOME:foo.bar", storage.make_canonical("foo.bar").unwrap());
712        assert_eq!("SOME:/foo.bar", storage.make_canonical("/foo.bar").unwrap());
713
714        assert_eq!("MEMORY:a", storage.make_canonical("memory:a").unwrap());
715        assert_eq!("MEMORY:/Abc", storage.make_canonical("memory:/Abc").unwrap());
716        assert_eq!("OTHER:a", storage.make_canonical("Other:a").unwrap());
717        assert_eq!("OTHER:/Abc", storage.make_canonical("Other:/Abc").unwrap());
718    }
719
720    #[test]
721    fn test_storage_make_canonical_errors() {
722        let storage = Storage::default();
723        assert_eq!(
724            "Invalid drive name 'a\\b'",
725            format!("{}", storage.make_canonical("a\\b:c").unwrap_err())
726        );
727    }
728
729    #[test]
730    fn test_storage_attach_ok() {
731        let mut storage = Storage::default();
732        storage.attach("zzz1", "z://", Box::from(InMemoryDrive::default())).unwrap();
733        storage.attach("A4", "z://", Box::from(InMemoryDrive::default())).unwrap();
734
735        assert_eq!("MEMORY:/", storage.cwd());
736        assert_eq!(["A4", "MEMORY", "ZZZ1"], drive_names(&storage).as_slice());
737    }
738
739    #[test]
740    fn test_storage_attach_invalid_name() {
741        let mut storage = Storage::default();
742        assert_eq!(
743            "Invalid drive name 'a:b'",
744            format!(
745                "{}",
746                storage.attach("a:b", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
747            )
748        );
749    }
750
751    #[test]
752    fn test_storage_attach_already_attached() {
753        let mut storage = Storage::default();
754        assert_eq!(
755            "Drive 'memory' is already mounted",
756            format!(
757                "{}",
758                storage.attach("memory", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
759            )
760        );
761
762        storage.attach("new", "z://", Box::from(InMemoryDrive::default())).unwrap();
763        assert_eq!(
764            "Drive 'New' is already mounted",
765            format!(
766                "{}",
767                storage.attach("New", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
768            )
769        );
770    }
771
772    #[test]
773    fn test_storage_has_scheme() {
774        let mut storage = Storage::default();
775        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
776        assert!(storage.has_scheme("fake"));
777        assert!(!storage.has_scheme("fakes"));
778    }
779
780    #[test]
781    fn test_storage_mount_ok() {
782        let mut storage = Storage::default();
783        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
784        storage.mount("a", "memory://").unwrap();
785        storage.mount("z", "fAkE://").unwrap();
786
787        assert_eq!(["A", "MEMORY", "Z"], drive_names(&storage).as_slice());
788    }
789
790    #[test]
791    fn test_storage_mount_path_redirection() {
792        let root = tempfile::tempdir().unwrap();
793        let dir1 = root.path().join("first");
794        let dir2 = root.path().join("second");
795
796        let mut storage = Storage::default();
797        storage.register_scheme("file", Box::from(DirectoryDriveFactory::default()));
798        storage.mount("c", &format!("file://{}", dir1.display())).unwrap();
799        storage.mount("d", &format!("file://{}", dir2.display())).unwrap();
800
801        block_on(storage.put("c:file1.txt", "hi")).unwrap();
802        block_on(storage.put("d:file2.txt", "bye")).unwrap();
803
804        assert!(dir1.join("file1.txt").exists());
805        assert!(!dir1.join("file2.txt").exists());
806        assert!(!dir2.join("file1.txt").exists());
807        assert!(dir2.join("file2.txt").exists());
808    }
809
810    #[test]
811    fn test_storage_mount_unknown_scheme() {
812        let mut storage = Storage::default();
813        assert_eq!(
814            "Unknown mount scheme 'fake'",
815            format!("{}", storage.mount("a", "fake://abc").unwrap_err())
816        );
817    }
818
819    #[test]
820    fn test_storage_mount_invalid_path_for_scheme() {
821        let mut storage = Storage::default();
822        assert_eq!(
823            "Cannot specify a path to mount an in-memory drive",
824            format!("{}", storage.mount("a", "memory://abc").unwrap_err())
825        );
826    }
827
828    #[test]
829    fn test_storage_unmount_ok() {
830        let mut storage = Storage::default();
831        storage.mount("other", "memory://").unwrap();
832        assert_eq!("MEMORY:/", storage.cwd());
833        assert_eq!(["MEMORY", "OTHER"], drive_names(&storage).as_slice());
834
835        storage.unmount("other").unwrap();
836        assert_eq!("MEMORY:/", storage.cwd());
837        assert_eq!(["MEMORY"], drive_names(&storage).as_slice());
838    }
839
840    #[test]
841    fn test_storage_unmount_not_mounted_error() {
842        let mut storage = Storage::default();
843        assert_eq!(
844            "Drive 'foo' is not mounted",
845            format!("{}", storage.unmount("foo").unwrap_err())
846        );
847    }
848
849    #[test]
850    fn test_storage_unmount_current_drive_error() {
851        let mut storage = Storage::default();
852        storage.mount("other", "memory://").unwrap();
853        assert_eq!(
854            "Cannot unmount the current drive 'memory'",
855            format!("{}", storage.unmount("memory").unwrap_err())
856        );
857        storage.cd("other:").unwrap();
858        storage.unmount("memory").unwrap();
859        assert_eq!("OTHER:/", storage.cwd());
860        assert_eq!(["OTHER"], drive_names(&storage).as_slice());
861    }
862
863    #[test]
864    fn test_storage_mounted() {
865        let mut storage = Storage::default();
866        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
867        storage.mount("z", "fAkE://").unwrap();
868
869        let mut exp_info = BTreeMap::default();
870        exp_info.insert("MEMORY", "memory://");
871        exp_info.insert("Z", "fAkE://");
872        assert_eq!(exp_info, storage.mounted());
873    }
874
875    #[test]
876    fn test_storage_cd_and_cwd_ok() {
877        let mut storage = Storage::default();
878        storage.mount("other", "memory://").unwrap();
879        assert_eq!("MEMORY:/", storage.cwd());
880        storage.cd("other:/").unwrap();
881        assert_eq!("OTHER:/", storage.cwd());
882        storage.cd("memory:").unwrap();
883        assert_eq!("MEMORY:/", storage.cwd());
884    }
885
886    #[test]
887    fn test_storage_cd_errors() {
888        let mut storage = Storage::default();
889        assert_eq!("Invalid drive name ''", format!("{}", storage.cd(":foo").unwrap_err()));
890        assert_eq!("Invalid path 'a:b\\c'", format!("{}", storage.cd("a:b\\c").unwrap_err()));
891        assert_eq!("Cannot cd to a file", format!("{}", storage.cd("foo:bar.bas").unwrap_err()));
892        assert_eq!("Drive 'A' is not mounted", format!("{}", storage.cd("a:").unwrap_err()));
893    }
894
895    #[test]
896    fn test_storage_file_ops_with_absolute_paths() {
897        let mut storage = Storage::default();
898        storage.mount("other", "memory://").unwrap();
899
900        block_on(storage.put("other:/f1", "some text")).unwrap();
901        block_on(storage.put("other:f2", "other text")).unwrap();
902        {
903            // Ensure that the put operations were routed to the correct objects.
904            let memory_drive = storage.drives.get(&DriveKey::new("memory").unwrap()).unwrap();
905            assert_eq!(0, block_on(memory_drive.drive.enumerate()).unwrap().dirents().len());
906            let other_drive = storage.drives.get(&DriveKey::new("other").unwrap()).unwrap();
907            assert_eq!(2, block_on(other_drive.drive.enumerate()).unwrap().dirents().len());
908        }
909
910        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
911        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
912        assert_eq!(2, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
913        assert_eq!(2, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
914
915        assert_eq!("some text", block_on(storage.get("OTHER:f1")).unwrap());
916        assert_eq!("other text", block_on(storage.get("OTHER:/f2")).unwrap());
917
918        block_on(storage.delete("other:/f2")).unwrap();
919        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
920        assert_eq!(1, block_on(storage.enumerate("other:")).unwrap().dirents().len());
921        block_on(storage.delete("other:f1")).unwrap();
922        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
923        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
924    }
925
926    #[test]
927    fn test_storage_file_ops_with_relative_paths() {
928        let mut storage = Storage::default();
929        storage.mount("other", "memory://").unwrap();
930
931        block_on(storage.put("/f1", "some text")).unwrap();
932        block_on(storage.put("f2", "other text")).unwrap();
933        {
934            // Ensure that the put operations were routed to the correct objects.
935            let memory_drive = storage.drives.get(&DriveKey::new("memory").unwrap()).unwrap();
936            assert_eq!(2, block_on(memory_drive.drive.enumerate()).unwrap().dirents().len());
937            let other_drive = storage.drives.get(&DriveKey::new("other").unwrap()).unwrap();
938            assert_eq!(0, block_on(other_drive.drive.enumerate()).unwrap().dirents().len());
939        }
940
941        assert_eq!(2, block_on(storage.enumerate("")).unwrap().dirents().len());
942        assert_eq!(2, block_on(storage.enumerate("/")).unwrap().dirents().len());
943        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
944        assert_eq!(0, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
945
946        assert_eq!("some text", block_on(storage.get("f1")).unwrap());
947        assert_eq!("other text", block_on(storage.get("/f2")).unwrap());
948
949        block_on(storage.delete("/f2")).unwrap();
950        assert_eq!(1, block_on(storage.enumerate("")).unwrap().dirents().len());
951        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
952        block_on(storage.delete("f1")).unwrap();
953        assert_eq!(0, block_on(storage.enumerate("")).unwrap().dirents().len());
954        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
955    }
956
957    #[test]
958    fn test_storage_delete_errors() {
959        let mut storage = Storage::default();
960        assert_eq!(
961            "Invalid drive name ''",
962            format!("{}", block_on(storage.delete(":foo")).unwrap_err())
963        );
964        assert_eq!(
965            "Invalid path 'a:b\\c'",
966            format!("{}", block_on(storage.delete("a:b\\c")).unwrap_err())
967        );
968        assert_eq!(
969            "Missing file name in path 'a:'",
970            format!("{}", block_on(storage.delete("a:")).unwrap_err())
971        );
972    }
973
974    #[test]
975    fn test_storage_enumerate_errors() {
976        let storage = Storage::default();
977        assert_eq!(
978            "Invalid drive name ''",
979            format!("{}", block_on(storage.enumerate(":foo")).unwrap_err())
980        );
981        assert_eq!(
982            "Invalid path 'a:b\\c'",
983            format!("{}", block_on(storage.enumerate("a:b\\c")).unwrap_err())
984        );
985        assert_eq!(
986            "Location 'a:/foo' is not a directory",
987            format!("{}", block_on(storage.enumerate("a:/foo")).unwrap_err())
988        );
989    }
990
991    #[test]
992    fn test_storage_get_errors() {
993        let storage = Storage::default();
994        assert_eq!(
995            "Invalid drive name ''",
996            format!("{}", block_on(storage.get(":foo")).unwrap_err())
997        );
998        assert_eq!(
999            "Invalid path 'a:b\\c'",
1000            format!("{}", block_on(storage.get("a:b\\c")).unwrap_err())
1001        );
1002        assert_eq!(
1003            "Missing file name in path 'a:'",
1004            format!("{}", block_on(storage.get("a:")).unwrap_err())
1005        );
1006    }
1007
1008    #[test]
1009    fn test_storage_put_errors() {
1010        let mut storage = Storage::default();
1011        assert_eq!(
1012            "Invalid drive name ''",
1013            format!("{}", block_on(storage.put(":foo", "")).unwrap_err())
1014        );
1015        assert_eq!(
1016            "Invalid path 'a:b\\c'",
1017            format!("{}", block_on(storage.put("a:b\\c", "")).unwrap_err())
1018        );
1019        assert_eq!(
1020            "Missing file name in path 'a:'",
1021            format!("{}", block_on(storage.put("a:", "")).unwrap_err())
1022        );
1023    }
1024
1025    #[test]
1026    fn test_storage_system_path_ok() {
1027        let dir = tempfile::tempdir().unwrap();
1028        let dir = dir.path().canonicalize().unwrap();
1029
1030        let mut storage = Storage::default();
1031        storage
1032            .attach(
1033                "c",
1034                &format!("file://{}", dir.display()),
1035                Box::from(DirectoryDrive::new(dir.clone()).unwrap()),
1036            )
1037            .unwrap();
1038
1039        assert!(storage.system_path("memory:/foo").unwrap().is_none());
1040        assert_eq!(dir.join("some name"), storage.system_path("c:/some name").unwrap().unwrap());
1041        assert_eq!(dir.join("xyz"), storage.system_path("c:xyz").unwrap().unwrap());
1042    }
1043
1044    #[test]
1045    fn test_storage_system_path_of_cwd() {
1046        let dir = tempfile::tempdir().unwrap();
1047        let dir = dir.path().canonicalize().unwrap();
1048
1049        let mut storage = Storage::default();
1050        storage
1051            .attach(
1052                "c",
1053                &format!("file://{}", dir.display()),
1054                Box::from(DirectoryDrive::new(dir.clone()).unwrap()),
1055            )
1056            .unwrap();
1057
1058        assert!(storage.system_path(&storage.cwd()).unwrap().is_none());
1059
1060        storage.cd("c:/").unwrap();
1061        assert_eq!(dir, storage.system_path(&storage.cwd()).unwrap().unwrap());
1062    }
1063
1064    #[test]
1065    fn test_storage_system_path_errors() {
1066        let dir = tempfile::tempdir().unwrap();
1067        let dir = dir.path();
1068
1069        let mut storage = Storage::default();
1070        storage
1071            .attach(
1072                "c",
1073                &format!("file://{}", dir.display()),
1074                Box::from(DirectoryDrive::new(dir).unwrap()),
1075            )
1076            .unwrap();
1077
1078        assert_eq!(
1079            "Too many / separators in path 'c:a/b'",
1080            format!("{}", storage.system_path("c:a/b").unwrap_err())
1081        );
1082        assert_eq!("Invalid path 'c:..'", format!("{}", storage.system_path("c:..").unwrap_err()));
1083    }
1084}