flep 0.2.0

File transfer protocol (FTP) library
Documentation
use Error;
use super::FileSystem;

use std::collections::HashMap;
use std::path::Path;

const ROOT_DIR_NAME: &'static str = "";

/// An in-memory filesystem.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Memory
{
    /// The root directory.
    root: Node,
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct Node
{
    name: String,
    kind: NodeKind,
}

#[derive(Clone, Debug, PartialEq, Eq)]
enum NodeKind
{
    File(File),
    Directory(Directory),
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct File
{
    data: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
struct Directory
{
    nodes: HashMap<String, Node>,
}

impl Memory
{
    /// Creates a new in-memory filesystem.
    pub fn new() -> Self {
        Memory {
            root: Node {
                name: ROOT_DIR_NAME.to_owned(),
                kind: NodeKind::Directory(Directory { nodes: HashMap::new() })
            },
        }
    }

    #[cfg(test)]
    fn root_dir_mut(&mut self) -> &mut Directory {
        if let NodeKind::Directory(ref mut dir) = self.root.kind { dir } else { unreachable!() }
    }

    fn find_parent_and_name(&self, path: &Path) -> Result<(&Node, String), Error> {
        if let Some(parent) = path.parent() {
            let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
            Ok((self.find_node(parent)?, file_name))
        } else {
            // FIXME: better error handling.
            panic!("no parent");
        }
    }

    fn find_parent_and_name_mut(&mut self, path: &Path) -> Result<(&mut Node, String), Error> {
        if let Some(parent) = path.parent() {
            let file_name = path.file_name().unwrap().to_str().unwrap().to_owned();
            Ok((self.find_node_mut(parent)?, file_name))
        } else {
            // FIXME: better error handling.
            panic!("no parent");
        }
    }

    fn find_node(&self, path: &Path) -> Result<&Node, Error> {
        let mut parts: Vec<_> = path.iter().map(|s| s.to_str().unwrap()).collect();

        // Skip the '/' if it exists.
        if parts.first() == Some(&"/") {
            parts = parts[1..].to_owned();
        }

        if parts.is_empty() {
            Ok(&self.root)
        } else {
            let mut the_parts = vec![ROOT_DIR_NAME];
            the_parts.extend(parts);
            let node = self.root.find_node(the_parts);

            if let Some(node) = node { Ok(node) } else { panic!("path does not exist") }
        }
    }

    fn find_node_mut(&mut self, path: &Path) -> Result<&mut Node, Error> {
        let mut parts: Vec<_> = path.iter().map(|s| s.to_str().unwrap()).collect();

        // Skip the '/' if it exists.
        if parts.first() == Some(&"/") {
            parts = parts[1..].to_owned();
        }

        if parts.is_empty() {
            Ok(&mut self.root)
        } else {
            let mut the_parts = vec![ROOT_DIR_NAME];
            the_parts.extend(parts);
            let node = self.root.find_node_mut(the_parts);

            if let Some(node) = node { Ok(node) } else { panic!("path does not exist") }
        }
    }
}

impl Node
{
    fn find_node(&self, parts: Vec<&str>) -> Option<&Self> {
        if parts == vec![&self.name] { return Some(self) };

        if let NodeKind::Directory(ref dir) = self.kind {
            let child_parts = parts[1..].to_owned();

            for node in dir.nodes.values() { if let Some(node) = node.find_node(child_parts.clone()) {
                return Some(node)
            }}
        }
        None
    }

    fn find_node_mut(&mut self, parts: Vec<&str>) -> Option<&mut Self> {
        if parts == vec![&self.name] { return Some(self) };

        if let NodeKind::Directory(ref mut dir) = self.kind {
            let child_parts = parts[1..].to_owned();

            for node in dir.nodes.values_mut() { if let Some(node) = node.find_node_mut(child_parts.clone()) {
                return Some(node)
            }}
        }
        None
    }
}

impl Directory
{
    #[cfg(test)]
    pub fn new() -> Self {
        Directory { nodes: HashMap::new() }
    }

    #[cfg(test)]
    pub fn add(mut self, node: Node) -> Self {
        self.nodes.insert(node.name.clone(), node);
        self
    }
}

impl FileSystem for Memory
{
    fn list(&self, path: &Path)
        -> Result<Vec<String>, Error> {
        let parent_node = self.find_node(path)?;

        match parent_node.kind {
            NodeKind::Directory(ref dir) => {
                Ok(dir.nodes.values().map(|node| node.name.clone()).collect())
            },
            // FIXME: better error handling
            NodeKind::File(..) => panic!("this is not a directory"),
        }
    }

    fn create_dir(&mut self, path: &Path) -> Result<(), Error> {
        let (parent, file_name) = self.find_parent_and_name_mut(path)?;

        if let NodeKind::Directory(ref mut dir) = parent.kind {
            dir.nodes.insert(file_name.clone(), Node {
                name: file_name,
                kind: NodeKind::Directory(Directory { nodes: HashMap::new() }),
            });
            Ok(())
        } else {
            // FIXME: better error handling
            panic!("not a dir")
        }
    }

    fn write_file(&mut self, path: &Path, data: Vec<u8>) -> Result<(), Error> {
        let (parent, file_name) = self.find_parent_and_name_mut(path)?;

        if let NodeKind::Directory(ref mut dir) = parent.kind {
            dir.nodes.insert(file_name.clone(), Node {
                name: file_name,
                kind: NodeKind::File(File { data: data }),
            });
            Ok(())
        } else {
            // FIXME: better error handling
            panic!("parent must be a directory");
        }
    }

    fn read_file(&self, path: &Path) -> Result<Vec<u8>, Error> {
        let (parent, file_name) = self.find_parent_and_name(path)?;

        if let NodeKind::Directory(ref dir) = parent.kind {
            if let Some(ref node) = dir.nodes.get(&file_name) {
                match node.kind {
                    NodeKind::File(ref file) => {
                        Ok(file.data.clone())
                    },
                    NodeKind::Directory(..) => {
                        // FIXME: better error handling
                        panic!("cannot read a directory");
                    },
                }
            } else {
                // FIXME: better error handling
                panic!("file does not exist");
            }
        } else {
            // FIXME: better error handling
            panic!("parent must be a directory");
        }
    }
}

#[cfg(test)]
mod test
{
    pub use super::*;

    mod find_node {
        use super::super::{Node, NodeKind, File, Directory};
        pub use super::*;
        use std::path::Path;

        #[test]
        fn correctly_finds_empty_root() {
            let fs = Memory::new();
            assert_eq!(fs.find_node(&Path::new("/")).unwrap(), &fs.root);
        }

        #[test]
        fn correctly_finds_empty_string_as_root() {
            let fs = Memory::new();
            assert_eq!(fs.find_node(&Path::new("")).unwrap(), &fs.root);
        }

        #[test]
        fn correctly_finds_top_level_file() {
            let mut fs = Memory::new();
            let foo = Node {
                name: "foo".to_owned(),
                kind: NodeKind::File(File { data: vec![1,2,3] }),
            };

            fs.root_dir_mut().nodes.insert(foo.name.clone(), foo.clone());

            assert_eq!(fs.find_node(&Path::new("/foo")).unwrap(), &foo);
        }

        #[test]
        fn correctly_finds_top_level_directory() {
            let mut fs = Memory::new();
            let bar = Node {
                name: "bar".to_owned(),
                kind: NodeKind::Directory(Directory::new()),
            };

            fs.root_dir_mut().nodes.insert(bar.name.clone(), bar.clone());

            assert_eq!(fs.find_node(&Path::new("/bar")).unwrap(), &bar);
        }

        #[test]
        fn correctly_finds_nested_file() {
            let mut fs = Memory::new();
            let foo = Node {
                name: "foo".to_owned(),
                kind: NodeKind::Directory(Directory::new()),
            };

            let bar = Node {
                name: "bar".to_owned(),
                kind: NodeKind::Directory(Directory::new().add(foo.clone())),
            };

            fs.root_dir_mut().nodes.insert(bar.name.clone(), bar.clone());

            assert_eq!(fs.find_node(&Path::new("/bar/foo")).unwrap(), &foo);
        }
    }

    mod create_dir {
        use super::super::{Node, NodeKind, Directory, FileSystem};
        pub use super::*;
        use std::path::Path;

        #[test]
        fn correctly_creates_a_top_level_dir() {
            let mut fs = Memory::new();
            fs.create_dir(&Path::new("/bar")).unwrap();

            assert_eq!(fs.root, Node {
                name: super::super::ROOT_DIR_NAME.to_owned(),
                kind: NodeKind::Directory(Directory::new().add(Node {
                    name: "bar".to_owned(),
                    kind: NodeKind::Directory(Directory::new()),
                })),
            });
        }
    }

    mod write_file {
        use super::super::{Node, NodeKind, File, Directory, FileSystem};
        pub use super::*;
        use std::path::Path;

        #[test]
        fn correctly_creates_a_top_level_file() {
            let mut fs = Memory::new();

            fs.write_file(&Path::new("/foo.txt"), vec![1,2,3]).unwrap();

            assert_eq!(fs.root, Node {
                name: super::super::ROOT_DIR_NAME.to_owned(),
                kind: NodeKind::Directory(Directory::new().add(Node {
                    name: "foo.txt".to_owned(),
                    kind: NodeKind::File(File { data: vec![1,2,3] }),
                })),
            });
        }
    }

    mod read_file {
        pub use super::*;
        use super::super::FileSystem;
        use std::path::Path;

        #[test]
        fn correctly_reads_a_top_level_file() {
            let mut fs = Memory::new();

            fs.write_file(&Path::new("/foo.txt"), vec![1,2,3]).unwrap();
            assert_eq!(fs.read_file(&Path::new("/foo.txt")).unwrap(), vec![1,2,3]);
        }
    }
}