Skip to main content

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::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<Vec<u8>>;
154
155    /// Gets the ACLs of the file `_name`.
156    async fn get_acls(&self, _name: &str) -> io::Result<FileAcls> {
157        Err(io::Error::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: &[u8]) -> 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::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    /// Sets the leaf name of this path.
285    fn set_leaf_name(&mut self, name: &str) {
286        if self.path.starts_with('/') {
287            self.path.clear();
288            self.path.push('/');
289            self.path.push_str(name);
290        } else {
291            self.path.clear();
292            self.path.push_str(name);
293        }
294    }
295
296    /// Sets the file name extension as long as this location corresponds to a file and not a
297    /// directory and does not already have one.
298    ///
299    /// The `extension` must be provided in lowercase.
300    fn set_extension(&mut self, extension: &str) {
301        debug_assert_eq!(extension, extension.to_ascii_lowercase());
302
303        if let Some(name) = self.leaf_name() {
304            if name.is_empty() {
305                return;
306            }
307
308            if name.rfind('.').is_some() {
309                return;
310            }
311
312            // Attempt to determine a sensible extension based on the case of the leaf name,
313            // assuming that an all-uppercase basename wants an all-uppercase extension.  This is
314            // fragile on case-sensitive file systems, but there is not a lot we can do.
315            let mut as_uppercase = true;
316            for ch in name.chars() {
317                if ch.is_ascii_lowercase() {
318                    as_uppercase = false;
319                    break;
320                }
321            }
322
323            self.path.push('.');
324            if as_uppercase {
325                self.path.push_str(&extension.to_ascii_uppercase());
326            } else {
327                self.path.push_str(extension);
328            }
329        }
330    }
331}
332
333impl fmt::Display for Location {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        match &self.drive {
336            Some(drive) => write!(f, "{}:{}", drive.0, self.path),
337            None => self.path.fmt(f),
338        }
339    }
340}
341
342/// Trait to instantiate drives of a given type.
343pub trait DriveFactory {
344    /// Creates a new drive for `target`.
345    fn create(&self, target: &str) -> io::Result<Box<dyn Drive>>;
346}
347
348/// Given a mount URI, validates it and returns the `(scheme, path)` pair.
349fn split_uri(uri: &str) -> io::Result<(&str, &str)> {
350    match uri.find("://") {
351        Some(pos) if pos > 0 => Ok((&uri[0..pos], &uri[pos + 3..])),
352        _ => Err(io::Error::new(
353            io::ErrorKind::InvalidInput,
354            "Mount URI must be of the form scheme://path",
355        )),
356    }
357}
358
359/// Metadata for a mounted drive.
360struct MountedDrive {
361    uri: String,
362    drive: Box<dyn Drive>,
363}
364
365/// Storage subsystem representation.
366///
367/// At the moment, the storage subsystem is backed by a single drive, so this type is a wrapper
368/// for the `Drive` type.
369pub struct Storage {
370    /// Mapping of target scheme names to drive factories.
371    factories: HashMap<String, Box<dyn DriveFactory>>,
372
373    /// Mapping of drive names to drives.
374    drives: HashMap<DriveKey, MountedDrive>,
375
376    /// Name of the active drive, which must be present in `drives`.
377    current: DriveKey,
378}
379
380impl Default for Storage {
381    /// Creates a new storage subsytem backed by an in-memory drive.
382    fn default() -> Self {
383        let mut factories: HashMap<String, Box<dyn DriveFactory>> = HashMap::default();
384        factories.insert("memory".to_owned(), Box::from(InMemoryDriveFactory::default()));
385
386        let drive: Box<dyn Drive> = Box::from(InMemoryDrive::default());
387
388        let mut drives = HashMap::new();
389        let key = DriveKey::new("MEMORY").expect("Hardcoded drive name must be valid");
390        let mounted_drive = MountedDrive { uri: "memory://".to_owned(), drive };
391        drives.insert(key.clone(), mounted_drive);
392        Self { factories, drives, current: key }
393    }
394}
395
396impl Storage {
397    /// Registers a new drive `factory` to handle the `scheme`.  Must not have previously been
398    /// registered and the `scheme` must be in lowercase.
399    pub fn register_scheme(&mut self, scheme: &str, factory: Box<dyn DriveFactory>) {
400        assert_eq!(scheme.to_lowercase(), scheme);
401        let previous = self.factories.insert(scheme.to_owned(), factory);
402        assert!(previous.is_none(), "Tried to register {} twice", scheme);
403    }
404
405    /// Returns true if the `scheme` is already registered.
406    pub fn has_scheme(&self, scheme: &str) -> bool {
407        self.factories.contains_key(scheme)
408    }
409
410    /// Converts a `raw_location`, which needn't exist, to its canonical form.
411    pub fn make_canonical(&self, raw_location: &str) -> io::Result<String> {
412        let mut location = Location::new(raw_location)?;
413        if location.drive.is_none() {
414            location.drive = Some(self.current.clone());
415        }
416        Ok(location.to_string())
417    }
418
419    /// Converts a `raw_location`, which needn't exist but must represent a file (not a directory),
420    /// to its canonical form.
421    ///
422    /// If `extension` is not empty, adds it to the location if it didn't not yet have nay.
423    pub fn make_canonical_with_extension(
424        &self,
425        raw_location: &str,
426        extension: &str,
427    ) -> io::Result<String> {
428        let mut location = Location::new(raw_location)?;
429        if location.drive.is_none() {
430            location.drive = Some(self.current.clone());
431        }
432        if location.leaf_name().is_none() {
433            return Err(io::Error::new(
434                io::ErrorKind::NotFound,
435                format!("Missing file name in path '{}'", raw_location),
436            ));
437        }
438        location.set_extension(extension);
439        Ok(location.to_string())
440    }
441
442    /// Attaches a new `drive` with `name`, which was instantiated with `uri`.
443    ///
444    /// The `name` must be valid and must not yet have been registered.
445    fn attach(&mut self, name: &str, uri: &str, drive: Box<dyn Drive>) -> io::Result<()> {
446        let key = DriveKey::new(name)?;
447        if self.drives.contains_key(&key) {
448            return Err(io::Error::new(
449                io::ErrorKind::AlreadyExists,
450                format!("Drive '{}' is already mounted", name),
451            ));
452        }
453        let mounted_drive = MountedDrive { uri: uri.to_owned(), drive };
454        self.drives.insert(DriveKey::new(name)?, mounted_drive);
455        Ok(())
456    }
457
458    /// Instantiates and attaches a new `drive` with `name` that points to `uri`.
459    ///
460    /// The `name` must be valid and must not yet have been registered.
461    pub fn mount(&mut self, name: &str, uri: &str) -> io::Result<()> {
462        let (scheme, path) = split_uri(uri)?;
463        let drive = match self.factories.get(&scheme.to_lowercase()) {
464            Some(factory) => factory.create(path)?,
465            None => {
466                return Err(io::Error::new(
467                    io::ErrorKind::InvalidInput,
468                    format!("Unknown mount scheme '{}'", scheme),
469                ));
470            }
471        };
472        self.attach(name, uri, drive)
473    }
474
475    /// Detaches an existing drive named `name`.
476    ///
477    /// The drive `name` must exist, cannot be the current drive, and cannot be the last mounted
478    /// drive.
479    pub fn unmount(&mut self, name: &str) -> io::Result<()> {
480        let key = DriveKey::new(name)?;
481        if !self.drives.contains_key(&key) {
482            return Err(io::Error::new(
483                io::ErrorKind::NotFound,
484                format!("Drive '{}' is not mounted", name),
485            ));
486        }
487        if self.current == key {
488            return Err(io::Error::new(
489                io::ErrorKind::AlreadyExists,
490                format!("Cannot unmount the current drive '{}'", name),
491            ));
492        }
493        assert!(
494            self.drives.len() > 1,
495            "There must be more than one drive if the current drive is not the given name"
496        );
497        self.drives.remove(&key).expect("Drive presence in map checked above");
498        Ok(())
499    }
500
501    /// Returns information about the mounted drives as a mapping of drive names to the URIs that
502    /// were used to mount them.
503    pub fn mounted(&self) -> BTreeMap<&str, &str> {
504        let mut info = BTreeMap::new();
505        for (name, mounted_drive) in &self.drives {
506            info.insert(name.0.as_str(), mounted_drive.uri.as_str());
507        }
508        info
509    }
510
511    /// Changes the current location.
512    ///
513    /// Given that we currently do not support directories, the location can only be of the forms
514    /// `DRIVE:` or `DRIVE:/`.
515    pub fn cd(&mut self, location: &str) -> io::Result<()> {
516        let location = Location::new(location)?;
517        if location.leaf_name().is_some() {
518            return Err(io::Error::new(io::ErrorKind::InvalidInput, "Cannot cd to a file"));
519        }
520
521        match location.drive {
522            Some(drive) => {
523                if !self.drives.contains_key(&drive) {
524                    return Err(io::Error::new(
525                        io::ErrorKind::AlreadyExists,
526                        format!("Drive '{}' is not mounted", drive),
527                    ));
528                }
529                self.current = drive;
530                Ok(())
531            }
532            None => Ok(()),
533        }
534    }
535
536    /// Returns the current location, used to resolve relative paths.
537    pub fn cwd(&self) -> String {
538        Location::with_drive_root(self.current.clone()).to_string()
539    }
540
541    /// Returns the drive referenced by `location`, or an error if it doesn't exist.
542    fn get_drive(&self, location: &Location) -> io::Result<&dyn Drive> {
543        match &location.drive {
544            Some(key) => match self.drives.get(key) {
545                Some(mounted_drive) => Ok(mounted_drive.drive.as_ref()),
546                None => Err(io::Error::new(
547                    io::ErrorKind::NotFound,
548                    format!("Drive '{}' is not mounted", key),
549                )),
550            },
551            None => Ok(self
552                .drives
553                .get(&self.current)
554                .expect("Current drive out of sync")
555                .drive
556                .as_ref()),
557        }
558    }
559
560    /// Returns the drive referenced by `location`, or an error if it doesn't exist.
561    fn get_drive_mut(&mut self, location: &Location) -> io::Result<&mut dyn Drive> {
562        match &location.drive {
563            Some(key) => match self.drives.get_mut(key) {
564                Some(mounted_drive) => Ok(mounted_drive.drive.as_mut()),
565                None => Err(io::Error::new(
566                    io::ErrorKind::NotFound,
567                    format!("Drive '{}' is not mounted", key),
568                )),
569            },
570            None => Ok(self
571                .drives
572                .get_mut(&self.current)
573                .expect("Current drive out of sync")
574                .drive
575                .as_mut()),
576        }
577    }
578
579    /// Deletes the program given by `raw_location`.
580    pub async fn delete(&mut self, raw_location: &str) -> io::Result<()> {
581        let location = Location::new(raw_location)?;
582        match location.leaf_name() {
583            Some(name) => self.get_drive_mut(&location)?.delete(name).await,
584            None => Err(io::Error::new(
585                io::ErrorKind::NotFound,
586                format!("Missing file name in path '{}'", raw_location),
587            )),
588        }
589    }
590
591    /// Returns a sorted list of the entries in `raw_location` and their metadata.
592    pub async fn enumerate(&self, raw_location: &str) -> io::Result<DriveFiles> {
593        let location = Location::new(raw_location)?;
594        match location.leaf_name() {
595            Some(_) => Err(io::Error::new(
596                io::ErrorKind::NotFound,
597                format!("Location '{}' is not a directory", raw_location),
598            )),
599            None => self.get_drive(&location)?.enumerate().await,
600        }
601    }
602
603    /// Loads the contents of the program given by `location`.  `raw_location` is the
604    /// string that the user provided and is used for error reporting.
605    async fn get_location(&self, raw_location: &str, location: &Location) -> io::Result<Vec<u8>> {
606        match location.leaf_name() {
607            Some(name) => self.get_drive(location)?.get(name).await,
608            None => Err(io::Error::new(
609                io::ErrorKind::NotFound,
610                format!("Missing file name in path '{}'", raw_location),
611            )),
612        }
613    }
614
615    /// Loads the contents of the program given by `raw_location`.
616    pub async fn get(&self, raw_location: &str) -> io::Result<Vec<u8>> {
617        let location = Location::new(raw_location)?;
618        self.get_location(raw_location, &location).await
619    }
620
621    /// Gets the ACLs of the file `raw_location`.
622    pub async fn get_acls(&self, raw_location: &str) -> io::Result<FileAcls> {
623        let location = Location::new(raw_location)?;
624        match location.leaf_name() {
625            Some(name) => self.get_drive(&location)?.get_acls(name).await,
626            None => Err(io::Error::new(
627                io::ErrorKind::NotFound,
628                format!("Missing file name in path '{}'", raw_location),
629            )),
630        }
631    }
632
633    /// Saves the in-memory program given by `content` into `location`.  `raw_location` is the
634    /// string that the user provided and is used for error reporting.
635    async fn put_location(
636        &mut self,
637        raw_location: &str,
638        location: &Location,
639        content: &[u8],
640    ) -> io::Result<()> {
641        match location.leaf_name() {
642            Some(name) => self.get_drive_mut(location)?.put(name, content).await,
643            None => Err(io::Error::new(
644                io::ErrorKind::NotFound,
645                format!("Missing file name in path '{}'", raw_location),
646            )),
647        }
648    }
649
650    /// Saves the in-memory program given by `content` into `raw_location`.
651    pub async fn put(&mut self, raw_location: &str, content: &[u8]) -> io::Result<()> {
652        let location = Location::new(raw_location)?;
653        self.put_location(raw_location, &location, content).await
654    }
655
656    /// Updates the ACLs of the file `raw_location` by extending them with the contents of `add` and
657    /// removing the existing entries listed in `remove`.
658    pub async fn update_acls(
659        &mut self,
660        raw_location: &str,
661        add: &FileAcls,
662        remove: &FileAcls,
663    ) -> io::Result<()> {
664        let location = Location::new(raw_location)?;
665        match location.leaf_name() {
666            Some(name) => self.get_drive_mut(&location)?.update_acls(name, add, remove).await,
667            None => Err(io::Error::new(
668                io::ErrorKind::NotFound,
669                format!("Missing file name in path '{}'", raw_location),
670            )),
671        }
672    }
673
674    /// Gets the system-addressable path of `raw_location`, if any.
675    pub fn system_path(&self, raw_location: &str) -> io::Result<Option<PathBuf>> {
676        let location = Location::new(raw_location)?;
677        match location.leaf_name() {
678            Some(name) => Ok(self.get_drive(&location)?.system_path(name)),
679            None => Ok(self.get_drive(&location)?.system_path("")),
680        }
681    }
682
683    /// Copies file `src` to `dest`.
684    pub async fn copy(&mut self, raw_src: &str, raw_dest: &str) -> io::Result<()> {
685        let src = Location::new(raw_src)?;
686        let src_name = match src.leaf_name() {
687            Some(name) => name,
688            None => {
689                return Err(io::Error::new(
690                    io::ErrorKind::NotFound,
691                    format!("Missing file name in copy source path '{}'", raw_src),
692                ));
693            }
694        };
695
696        let mut dest = Location::new(raw_dest)?;
697        if dest.leaf_name().is_none() {
698            dest.set_leaf_name(src_name);
699        }
700
701        let content = self.get_location(raw_src, &src).await?;
702        self.put_location(raw_dest, &dest, &content).await
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709    use futures_lite::future::block_on;
710
711    #[test]
712    fn test_split_uri_ok() {
713        assert_eq!(("the-scheme", ""), split_uri("the-scheme://").unwrap());
714        assert_eq!(("foo", "/some:/target"), split_uri("foo:///some:/target").unwrap());
715        assert_eq!(("foo", "bar://baz"), split_uri("foo://bar://baz").unwrap());
716    }
717
718    #[test]
719    fn test_split_uri_errors() {
720        for uri in &["", "://abc", "foo:/", "bar//", "/baz/"] {
721            assert_eq!(
722                "Mount URI must be of the form scheme://path",
723                format!("{}", split_uri(uri).unwrap_err())
724            );
725        }
726    }
727
728    #[test]
729    fn test_drivekey_new() {
730        assert_eq!(DriveKey("FOO".to_owned()), DriveKey::new("foo").unwrap());
731        assert_eq!(DriveKey("A".to_owned()), DriveKey::new("A".to_owned()).unwrap());
732    }
733
734    #[test]
735    fn test_drivekey_errors() {
736        assert_eq!("Invalid drive name ''", format!("{}", DriveKey::new("").unwrap_err()));
737        assert_eq!("Invalid drive name 'a:b'", format!("{}", DriveKey::new("a:b").unwrap_err()));
738        assert_eq!("Invalid drive name 'a\\b'", format!("{}", DriveKey::new("a\\b").unwrap_err()));
739        assert_eq!("Invalid drive name 'a/b'", format!("{}", DriveKey::new("a/b").unwrap_err()));
740        assert_eq!("Invalid drive name 'a.b'", format!("{}", DriveKey::new("a.b").unwrap_err()));
741    }
742
743    #[test]
744    fn test_location_new_ok() {
745        fn check(exp_drive: Option<&str>, exp_path: &str, input: &str) {
746            let rp = Location::new(input).unwrap();
747            assert_eq!(exp_drive.map(|s| DriveKey::new(s).unwrap()), rp.drive);
748            assert_eq!(exp_path, rp.path);
749        }
750
751        check(None, "/", "/");
752        check(None, "/foo.bas", "/foo.bas");
753
754        check(Some("A"), "/", "a:");
755        check(Some("ABC"), "/foo.bas", "abc:/foo.bas");
756        check(Some("ABC"), "Foo.Bas", "abc:Foo.Bas");
757    }
758
759    #[test]
760    fn test_location_new_errors() {
761        fn check(exp_error: &str, input: &str) {
762            let e = Location::new(input).unwrap_err();
763            assert_eq!(io::ErrorKind::InvalidInput, e.kind());
764            assert_eq!(exp_error, format!("{}", e));
765        }
766
767        check("Too many : separators in path 'a:b:c'", "a:b:c");
768
769        check("Invalid drive name ''", ":");
770        check("Invalid drive name 'a\\b'", "a\\b:");
771        check("Invalid drive name 'a/b'", "a/b:");
772        check("Invalid drive name 'a.b'", "a.b:");
773
774        check("Invalid path 'a:\\'", "a:\\");
775        check("Invalid path 'a:.'", "a:.");
776        check("Invalid path 'a:..'", "a:..");
777        check("Invalid path 'a:/.'", "a:/.");
778        check("Invalid path 'a:/..'", "a:/..");
779        check("Invalid path '.'", ".");
780        check("Invalid path '..'", "..");
781        check("Invalid path '/.'", "/.");
782        check("Invalid path '/..'", "/..");
783
784        check("Too many / separators in path 'a://.'", "a://.");
785        check("Too many / separators in path 'a:../'", "a:../");
786        check("Too many / separators in path 'a:b/c'", "a:b/c");
787        check("Too many / separators in path 'a:/b/c'", "a:/b/c");
788    }
789
790    #[test]
791    fn test_location_leaf_name() {
792        assert_eq!(None, Location::new("drv:/").unwrap().leaf_name());
793        assert_eq!(None, Location::new("drv:").unwrap().leaf_name());
794
795        assert_eq!(Some("abc.txt"), Location::new("a:/abc.txt").unwrap().leaf_name());
796        assert_eq!(Some("abc.txt"), Location::new("a:abc.txt").unwrap().leaf_name());
797        assert_eq!(Some("abc.txt"), Location::new("abc.txt").unwrap().leaf_name());
798    }
799
800    #[test]
801    fn test_location_set_leaf_name() {
802        let mut location = Location::new("drv:/").unwrap();
803        location.set_leaf_name("foo");
804        assert_eq!("DRV:/foo", format!("{}", location));
805
806        let mut location = Location::new("drv:").unwrap();
807        location.set_leaf_name("foo");
808        assert_eq!("DRV:/foo", format!("{}", location));
809
810        let mut location = Location::new("/bar").unwrap();
811        location.set_leaf_name("foo");
812        assert_eq!("/foo", format!("{}", location));
813
814        let mut location = Location::new("bar").unwrap();
815        location.set_leaf_name("foo");
816        assert_eq!("foo", format!("{}", location));
817    }
818
819    #[test]
820    fn test_location_set_extension() {
821        for (exp_location, raw_location, extension) in [
822            ("DRV:/", "drv:", "bas"),
823            ("DRV:/", "drv:/", "bas"),
824            ("foo.bas", "foo", "bas"),
825            ("foo.bas", "foo.bas", "bas"),
826            ("Foo.bas", "Foo", "bas"),
827            ("FOO.BAS", "FOO", "bas"),
828            ("foo.", "foo.", "bas"),
829            ("foo.other", "foo.other", "bas"),
830        ] {
831            let mut location = Location::new(raw_location).unwrap();
832            location.set_extension(extension);
833            assert_eq!(exp_location, location.to_string());
834        }
835    }
836
837    /// Convenience helper to obtain the sorted list of mounted drive names in canonical form.
838    fn drive_names(storage: &Storage) -> Vec<String> {
839        let mut names = storage.drives.keys().map(DriveKey::to_string).collect::<Vec<String>>();
840        names.sort();
841        names
842    }
843
844    #[test]
845    fn test_storage_default() {
846        let storage = Storage::default();
847        assert_eq!("MEMORY:/", storage.cwd());
848    }
849
850    #[test]
851    fn test_storage_make_canonical_ok() {
852        let mut storage = Storage::default();
853        storage.mount("some", "memory://").unwrap();
854
855        assert_eq!("MEMORY:/", storage.make_canonical("memory:").unwrap());
856
857        assert_eq!("MEMORY:foo.bar", storage.make_canonical("foo.bar").unwrap());
858        assert_eq!("MEMORY:/foo.bar", storage.make_canonical("/foo.bar").unwrap());
859        storage.cd("some:/").unwrap();
860        assert_eq!("SOME:foo.bar", storage.make_canonical("foo.bar").unwrap());
861        assert_eq!("SOME:/foo.bar", storage.make_canonical("/foo.bar").unwrap());
862
863        assert_eq!("MEMORY:a", storage.make_canonical("memory:a").unwrap());
864        assert_eq!("MEMORY:/Abc", storage.make_canonical("memory:/Abc").unwrap());
865        assert_eq!("OTHER:a", storage.make_canonical("Other:a").unwrap());
866        assert_eq!("OTHER:/Abc", storage.make_canonical("Other:/Abc").unwrap());
867    }
868
869    #[test]
870    fn test_storage_make_canonical_errors() {
871        let storage = Storage::default();
872        assert_eq!(
873            "Invalid drive name 'a\\b'",
874            format!("{}", storage.make_canonical("a\\b:c").unwrap_err())
875        );
876    }
877
878    #[test]
879    fn test_storage_make_canonical_with_extension_ok() {
880        let mut storage = Storage::default();
881        storage.mount("some", "memory://").unwrap();
882
883        assert_eq!("MEMORY:foo.bas", storage.make_canonical_with_extension("foo", "bas").unwrap());
884        assert_eq!(
885            "MEMORY:/foo.bar",
886            storage.make_canonical_with_extension("/foo.bar", "bas").unwrap()
887        );
888
889        assert_eq!(
890            "MEMORY:a.bas",
891            storage.make_canonical_with_extension("memory:a", "bas").unwrap()
892        );
893        assert_eq!(
894            "MEMORY:/a.bas",
895            storage.make_canonical_with_extension("memory:/a.bas", "bas").unwrap()
896        );
897    }
898
899    #[test]
900    fn test_storage_make_canonical_with_extension_errors() {
901        let storage = Storage::default();
902
903        assert_eq!(
904            "Missing file name in path 'memory:'",
905            format!("{}", storage.make_canonical_with_extension("memory:", "bas").unwrap_err()),
906        );
907
908        assert_eq!(
909            "Invalid drive name 'a\\b'",
910            format!("{}", storage.make_canonical_with_extension("a\\b:c", "bas").unwrap_err())
911        );
912    }
913
914    #[test]
915    fn test_storage_attach_ok() {
916        let mut storage = Storage::default();
917        storage.attach("zzz1", "z://", Box::from(InMemoryDrive::default())).unwrap();
918        storage.attach("A4", "z://", Box::from(InMemoryDrive::default())).unwrap();
919
920        assert_eq!("MEMORY:/", storage.cwd());
921        assert_eq!(["A4", "MEMORY", "ZZZ1"], drive_names(&storage).as_slice());
922    }
923
924    #[test]
925    fn test_storage_attach_invalid_name() {
926        let mut storage = Storage::default();
927        assert_eq!(
928            "Invalid drive name 'a:b'",
929            format!(
930                "{}",
931                storage.attach("a:b", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
932            )
933        );
934    }
935
936    #[test]
937    fn test_storage_attach_already_attached() {
938        let mut storage = Storage::default();
939        assert_eq!(
940            "Drive 'memory' is already mounted",
941            format!(
942                "{}",
943                storage.attach("memory", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
944            )
945        );
946
947        storage.attach("new", "z://", Box::from(InMemoryDrive::default())).unwrap();
948        assert_eq!(
949            "Drive 'New' is already mounted",
950            format!(
951                "{}",
952                storage.attach("New", "z://", Box::from(InMemoryDrive::default())).unwrap_err()
953            )
954        );
955    }
956
957    #[test]
958    fn test_storage_has_scheme() {
959        let mut storage = Storage::default();
960        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
961        assert!(storage.has_scheme("fake"));
962        assert!(!storage.has_scheme("fakes"));
963    }
964
965    #[test]
966    fn test_storage_mount_ok() {
967        let mut storage = Storage::default();
968        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
969        storage.mount("a", "memory://").unwrap();
970        storage.mount("z", "fAkE://").unwrap();
971
972        assert_eq!(["A", "MEMORY", "Z"], drive_names(&storage).as_slice());
973    }
974
975    #[test]
976    fn test_storage_mount_path_redirection() {
977        let root = tempfile::tempdir().unwrap();
978        let dir1 = root.path().join("first");
979        let dir2 = root.path().join("second");
980
981        let mut storage = Storage::default();
982        storage.register_scheme("file", Box::from(DirectoryDriveFactory::default()));
983        storage.mount("c", &format!("file://{}", dir1.display())).unwrap();
984        storage.mount("d", &format!("file://{}", dir2.display())).unwrap();
985
986        block_on(storage.put("c:file1.txt", b"hi")).unwrap();
987        block_on(storage.put("d:file2.txt", b"bye")).unwrap();
988
989        assert!(dir1.join("file1.txt").exists());
990        assert!(!dir1.join("file2.txt").exists());
991        assert!(!dir2.join("file1.txt").exists());
992        assert!(dir2.join("file2.txt").exists());
993    }
994
995    #[test]
996    fn test_storage_mount_unknown_scheme() {
997        let mut storage = Storage::default();
998        assert_eq!(
999            "Unknown mount scheme 'fake'",
1000            format!("{}", storage.mount("a", "fake://abc").unwrap_err())
1001        );
1002    }
1003
1004    #[test]
1005    fn test_storage_mount_invalid_path_for_scheme() {
1006        let mut storage = Storage::default();
1007        assert_eq!(
1008            "Cannot specify a path to mount an in-memory drive",
1009            format!("{}", storage.mount("a", "memory://abc").unwrap_err())
1010        );
1011    }
1012
1013    #[test]
1014    fn test_storage_unmount_ok() {
1015        let mut storage = Storage::default();
1016        storage.mount("other", "memory://").unwrap();
1017        assert_eq!("MEMORY:/", storage.cwd());
1018        assert_eq!(["MEMORY", "OTHER"], drive_names(&storage).as_slice());
1019
1020        storage.unmount("other").unwrap();
1021        assert_eq!("MEMORY:/", storage.cwd());
1022        assert_eq!(["MEMORY"], drive_names(&storage).as_slice());
1023    }
1024
1025    #[test]
1026    fn test_storage_unmount_not_mounted_error() {
1027        let mut storage = Storage::default();
1028        assert_eq!(
1029            "Drive 'foo' is not mounted",
1030            format!("{}", storage.unmount("foo").unwrap_err())
1031        );
1032    }
1033
1034    #[test]
1035    fn test_storage_unmount_current_drive_error() {
1036        let mut storage = Storage::default();
1037        storage.mount("other", "memory://").unwrap();
1038        assert_eq!(
1039            "Cannot unmount the current drive 'memory'",
1040            format!("{}", storage.unmount("memory").unwrap_err())
1041        );
1042        storage.cd("other:").unwrap();
1043        storage.unmount("memory").unwrap();
1044        assert_eq!("OTHER:/", storage.cwd());
1045        assert_eq!(["OTHER"], drive_names(&storage).as_slice());
1046    }
1047
1048    #[test]
1049    fn test_storage_mounted() {
1050        let mut storage = Storage::default();
1051        storage.register_scheme("fake", Box::from(InMemoryDriveFactory::default()));
1052        storage.mount("z", "fAkE://").unwrap();
1053
1054        let mut exp_info = BTreeMap::default();
1055        exp_info.insert("MEMORY", "memory://");
1056        exp_info.insert("Z", "fAkE://");
1057        assert_eq!(exp_info, storage.mounted());
1058    }
1059
1060    #[test]
1061    fn test_storage_cd_and_cwd_ok() {
1062        let mut storage = Storage::default();
1063        storage.mount("other", "memory://").unwrap();
1064        assert_eq!("MEMORY:/", storage.cwd());
1065        storage.cd("other:/").unwrap();
1066        assert_eq!("OTHER:/", storage.cwd());
1067        storage.cd("memory:").unwrap();
1068        assert_eq!("MEMORY:/", storage.cwd());
1069    }
1070
1071    #[test]
1072    fn test_storage_cd_errors() {
1073        let mut storage = Storage::default();
1074        assert_eq!("Invalid drive name ''", format!("{}", storage.cd(":foo").unwrap_err()));
1075        assert_eq!("Invalid path 'a:b\\c'", format!("{}", storage.cd("a:b\\c").unwrap_err()));
1076        assert_eq!("Cannot cd to a file", format!("{}", storage.cd("foo:bar.bas").unwrap_err()));
1077        assert_eq!("Drive 'A' is not mounted", format!("{}", storage.cd("a:").unwrap_err()));
1078    }
1079
1080    #[test]
1081    fn test_storage_file_ops_with_absolute_paths() {
1082        let mut storage = Storage::default();
1083        storage.mount("other", "memory://").unwrap();
1084
1085        block_on(storage.put("other:/f1", b"some text")).unwrap();
1086        block_on(storage.put("other:f2", b"other text")).unwrap();
1087        {
1088            // Ensure that the put operations were routed to the correct objects.
1089            let memory_drive = storage.drives.get(&DriveKey::new("memory").unwrap()).unwrap();
1090            assert_eq!(0, block_on(memory_drive.drive.enumerate()).unwrap().dirents().len());
1091            let other_drive = storage.drives.get(&DriveKey::new("other").unwrap()).unwrap();
1092            assert_eq!(2, block_on(other_drive.drive.enumerate()).unwrap().dirents().len());
1093        }
1094
1095        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
1096        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
1097        assert_eq!(2, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
1098        assert_eq!(2, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
1099
1100        assert_eq!(b"some text", block_on(storage.get("OTHER:f1")).unwrap().as_slice());
1101        assert_eq!(b"other text", block_on(storage.get("OTHER:/f2")).unwrap().as_slice());
1102
1103        block_on(storage.delete("other:/f2")).unwrap();
1104        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
1105        assert_eq!(1, block_on(storage.enumerate("other:")).unwrap().dirents().len());
1106        block_on(storage.delete("other:f1")).unwrap();
1107        assert_eq!(0, block_on(storage.enumerate("memory:")).unwrap().dirents().len());
1108        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
1109    }
1110
1111    #[test]
1112    fn test_storage_file_ops_with_relative_paths() {
1113        let mut storage = Storage::default();
1114        storage.mount("other", "memory://").unwrap();
1115
1116        block_on(storage.put("/f1", b"some text")).unwrap();
1117        block_on(storage.put("f2", b"other text")).unwrap();
1118        {
1119            // Ensure that the put operations were routed to the correct objects.
1120            let memory_drive = storage.drives.get(&DriveKey::new("memory").unwrap()).unwrap();
1121            assert_eq!(2, block_on(memory_drive.drive.enumerate()).unwrap().dirents().len());
1122            let other_drive = storage.drives.get(&DriveKey::new("other").unwrap()).unwrap();
1123            assert_eq!(0, block_on(other_drive.drive.enumerate()).unwrap().dirents().len());
1124        }
1125
1126        assert_eq!(2, block_on(storage.enumerate("")).unwrap().dirents().len());
1127        assert_eq!(2, block_on(storage.enumerate("/")).unwrap().dirents().len());
1128        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
1129        assert_eq!(0, block_on(storage.enumerate("other:/")).unwrap().dirents().len());
1130
1131        assert_eq!(b"some text", block_on(storage.get("f1")).unwrap().as_slice());
1132        assert_eq!(b"other text", block_on(storage.get("/f2")).unwrap().as_slice());
1133
1134        block_on(storage.delete("/f2")).unwrap();
1135        assert_eq!(1, block_on(storage.enumerate("")).unwrap().dirents().len());
1136        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
1137        block_on(storage.delete("f1")).unwrap();
1138        assert_eq!(0, block_on(storage.enumerate("")).unwrap().dirents().len());
1139        assert_eq!(0, block_on(storage.enumerate("other:")).unwrap().dirents().len());
1140    }
1141
1142    #[test]
1143    fn test_storage_delete_errors() {
1144        let mut storage = Storage::default();
1145        assert_eq!(
1146            "Invalid drive name ''",
1147            format!("{}", block_on(storage.delete(":foo")).unwrap_err())
1148        );
1149        assert_eq!(
1150            "Invalid path 'a:b\\c'",
1151            format!("{}", block_on(storage.delete("a:b\\c")).unwrap_err())
1152        );
1153        assert_eq!(
1154            "Missing file name in path 'a:'",
1155            format!("{}", block_on(storage.delete("a:")).unwrap_err())
1156        );
1157    }
1158
1159    #[test]
1160    fn test_storage_enumerate_errors() {
1161        let storage = Storage::default();
1162        assert_eq!(
1163            "Invalid drive name ''",
1164            format!("{}", block_on(storage.enumerate(":foo")).unwrap_err())
1165        );
1166        assert_eq!(
1167            "Invalid path 'a:b\\c'",
1168            format!("{}", block_on(storage.enumerate("a:b\\c")).unwrap_err())
1169        );
1170        assert_eq!(
1171            "Location 'a:/foo' is not a directory",
1172            format!("{}", block_on(storage.enumerate("a:/foo")).unwrap_err())
1173        );
1174    }
1175
1176    #[test]
1177    fn test_storage_get_errors() {
1178        let storage = Storage::default();
1179        assert_eq!(
1180            "Invalid drive name ''",
1181            format!("{}", block_on(storage.get(":foo")).unwrap_err())
1182        );
1183        assert_eq!(
1184            "Invalid path 'a:b\\c'",
1185            format!("{}", block_on(storage.get("a:b\\c")).unwrap_err())
1186        );
1187        assert_eq!(
1188            "Missing file name in path 'a:'",
1189            format!("{}", block_on(storage.get("a:")).unwrap_err())
1190        );
1191    }
1192
1193    #[test]
1194    fn test_storage_put_errors() {
1195        let mut storage = Storage::default();
1196        assert_eq!(
1197            "Invalid drive name ''",
1198            format!("{}", block_on(storage.put(":foo", b"")).unwrap_err())
1199        );
1200        assert_eq!(
1201            "Invalid path 'a:b\\c'",
1202            format!("{}", block_on(storage.put("a:b\\c", b"")).unwrap_err())
1203        );
1204        assert_eq!(
1205            "Missing file name in path 'a:'",
1206            format!("{}", block_on(storage.put("a:", b"")).unwrap_err())
1207        );
1208    }
1209
1210    #[test]
1211    fn test_storage_system_path_ok() {
1212        let dir = tempfile::tempdir().unwrap();
1213        let dir = dir.path().canonicalize().unwrap();
1214
1215        let mut storage = Storage::default();
1216        storage
1217            .attach(
1218                "c",
1219                &format!("file://{}", dir.display()),
1220                Box::from(DirectoryDrive::new(dir.clone()).unwrap()),
1221            )
1222            .unwrap();
1223
1224        assert!(storage.system_path("memory:/foo").unwrap().is_none());
1225        assert_eq!(dir.join("some name"), storage.system_path("c:/some name").unwrap().unwrap());
1226        assert_eq!(dir.join("xyz"), storage.system_path("c:xyz").unwrap().unwrap());
1227    }
1228
1229    #[test]
1230    fn test_storage_system_path_of_cwd() {
1231        let dir = tempfile::tempdir().unwrap();
1232        let dir = dir.path().canonicalize().unwrap();
1233
1234        let mut storage = Storage::default();
1235        storage
1236            .attach(
1237                "c",
1238                &format!("file://{}", dir.display()),
1239                Box::from(DirectoryDrive::new(dir.clone()).unwrap()),
1240            )
1241            .unwrap();
1242
1243        assert!(storage.system_path(&storage.cwd()).unwrap().is_none());
1244
1245        storage.cd("c:/").unwrap();
1246        assert_eq!(dir, storage.system_path(&storage.cwd()).unwrap().unwrap());
1247    }
1248
1249    #[test]
1250    fn test_storage_system_path_errors() {
1251        let dir = tempfile::tempdir().unwrap();
1252        let dir = dir.path();
1253
1254        let mut storage = Storage::default();
1255        storage
1256            .attach(
1257                "c",
1258                &format!("file://{}", dir.display()),
1259                Box::from(DirectoryDrive::new(dir).unwrap()),
1260            )
1261            .unwrap();
1262
1263        assert_eq!(
1264            "Too many / separators in path 'c:a/b'",
1265            format!("{}", storage.system_path("c:a/b").unwrap_err())
1266        );
1267        assert_eq!("Invalid path 'c:..'", format!("{}", storage.system_path("c:..").unwrap_err()));
1268    }
1269}