summaryrefslogblamecommitdiff
path: root/src/devices/file_device.rs
blob: 61966b1c8763144a88cc97afcb704097579908a9 (plain) (tree)

















































































                                                                          

                                                                                          




























































































































































































                                                                                        
                                  












































                                                                
                                                                                 

                                                       
                                                                               







                                
mod bedrock_file_path;
mod bedrock_path_buffer;
mod buffered_file;
mod directory_listing;
mod entry;
mod operations;

use buffered_file::BufferedFile;
use bedrock_file_path::BedrockFilePath;
use bedrock_path_buffer::BedrockPathBuffer;
use directory_listing::DirectoryListing;
use entry::{Entry, EntryType};
use operations::{create_file, move_entry, delete_entry};

use bedrock_core::*;

use std::path::{Component, Path, PathBuf};


pub struct FileDevice {
    pub base_path: PathBuf,
    pub default_path: PathBuf,

    pub entry_buffer: BedrockPathBuffer,
    pub action_buffer: BedrockPathBuffer,
    pub path_buffer: BedrockPathBuffer,

    pub entry: Option<(Entry, BedrockFilePath)>,
    pub cached_dir: Option<(Entry, BedrockFilePath)>,

    pub success: bool,
    pub pointer_write: u32,
    pub length_write: u32,

    pub enable_read: bool,
    pub enable_write: bool,
    pub enable_create: bool,
    pub enable_move: bool,
    pub enable_delete: bool,
}

impl FileDevice {
    pub fn new() -> Self {
        #[cfg(target_family = "unix")]
        let default_base: PathBuf = PathBuf::from("/");
        #[cfg(target_family = "windows")]
        let default_base: PathBuf = PathBuf::from("");

        // TODO: I'm not at all confident that the default path is correct
        //       when not being set as the current directory.
        Self {
            base_path: default_base,
            default_path: match std::env::current_dir() {
                Ok(dir) => PathBuf::from(dir),
                Err(_) => PathBuf::from(""),
            },

            entry_buffer:  BedrockPathBuffer::new(),
            action_buffer: BedrockPathBuffer::new(),
            path_buffer:   BedrockPathBuffer::new(),

            entry: None,
            cached_dir: None,

            success: false,
            pointer_write: 0,
            length_write: 0,

            enable_read: true,
            enable_write: true,
            enable_create: true,
            enable_move: true,
            enable_delete: false,
        }
    }

    /// Safely close the current entry, cleaning up entry variables.
    pub fn close(&mut self) {
        self.entry_buffer.clear();
        self.action_buffer.clear();
        self.path_buffer.clear();
        self.flush();

        if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) {
            // Prevent the selected child from persisting when loading from cache.
            dir.deselect_child();
            self.cached_dir = Some((Entry::Directory(dir), path));
        }
    }

    /// Open the entry at the given Bedrock path.
    pub fn open(&mut self, path: BedrockFilePath) -> Result<(), ()> {
        match path.entry_type() {
            Some(EntryType::File) => {
                let open_result = std::fs::OpenOptions::new()
                    .read(self.enable_read)
                    .write(self.enable_write)
                    .open(path.as_path());
                // Keep the current entry open if we can't open the new path.
                if let Ok(file) = open_result {
                    self.close();
                    self.path_buffer.populate(path.as_buffer());
                    self.entry = Some((Entry::File(BufferedFile::new(file)), path));
                    return Ok(());
                };
            }
            Some(EntryType::Directory) => {
                // Attempt to use the cached directory.
                if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) {
                    if cached_path == path {
                        self.close();
                        self.path_buffer.populate(cached_path.as_buffer());
                        self.entry = Some((dir, cached_path));
                        return Ok(());
                    }
                }
                // Keep the current entry open if we can't open the new path.
                if let Some(listing) = DirectoryListing::from_path(&path) {
                    self.close();
                    self.path_buffer.populate(path.as_buffer());
                    self.entry = Some((Entry::Directory(listing), path));
                    return Ok(());
                };
            }
            // The entry either doesn't exist or is not a file or directory.
            None => (),
        }
        return Err(());
    }

    /// Process a byte received from the entry port.
    pub fn write_to_entry_port(&mut self, byte: u8) {
        if let Some(buffer) = self.entry_buffer.write(byte) {
            self.close();
            match BedrockFilePath::from_buffer(buffer, &self.base_path) {
                Some(path) => self.success = self.open(path).is_ok(),
                None =>       self.success = false,
            };
        }
    }

    /// Process a byte received from the action port.
    pub fn write_to_action_port(&mut self, byte: u8) {
        if let Some(buffer) = self.action_buffer.write(byte) {
            let destination_blank = buffer[0] == 0x00;
            let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
            self.success = false;

            if let Some((_, source)) = &self.entry {
                if destination_blank {
                    if self.enable_delete {
                        self.success = delete_entry(&source.as_path());
                    }
                } else if let Some(dest) = destination {
                    if self.enable_move {
                        self.success = move_entry(&source.as_path(), &dest.as_path());
                    }
                }
            } else if let Some(dest) = destination {
                if self.enable_create {
                    self.success = create_file(&dest.as_path());
                }
            }
            self.close();
        }
    }

    /// Attempt to open the parent directory of the current entry.
    pub fn ascend_to_parent(&mut self) {
        if let Some((_, path)) = &self.entry {
            match path.parent() {
                Some(parent) => self.success = self.open(parent).is_ok(),
                None         => self.success = false,
            };
        } else {
            match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
                Some(default) => self.success = self.open(default).is_ok(),
                None          => self.success = false,
            };
        }
    }

    /// Attempt to open the selected child of the current directory.
    pub fn descend_to_child(&mut self) {
        if let Some((Entry::Directory(dir), _)) = &self.entry {
            match dir.child_path() {
                Some(child) => self.success = self.open(child).is_ok(),
                None        => self.success = false,
            };
        } else {
            self.success = false;
        }
    }

    /// Return true if the current entry is a directory.
    pub fn entry_type(&self) -> bool {
        match self.entry {
            Some((Entry::Directory(_), _)) => true,
            _ => false,
        }
    }

    /// Read a byte from the path buffer of the selected child.
    pub fn read_child_path(&mut self) -> u8 {
        match &mut self.entry {
            Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(),
            _ => 0,
        }
    }

    pub fn set_child_path(&mut self, byte: u8) {
        if let Some((Entry::Directory(dir), _)) = &mut self.entry {
            dir.child_path_buffer().set_pointer(byte);
        }
    }

    /// Return true if the selected child is a directory.
    pub fn child_type(&self) -> bool {
        match &self.entry {
            Some((Entry::Directory(dir), _)) => match dir.child_type() {
                Some(EntryType::Directory) => true,
                _ => false,
            }
            _ => false,
        }
    }

    /// Read a byte from the current file.
    pub fn read_byte(&mut self) -> u8 {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.read(),
            _ => 0,
        }
    }

    /// Writes a byte to the currently-open file.
    pub fn write_byte(&mut self, byte: u8) {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.write(byte),
            _ => (),
        }
    }

    pub fn pointer(&mut self) -> u32 {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.pointer(),
            Some((Entry::Directory(dir), _)) => dir.selected(),
            _ => 0,
        }
    }

    pub fn commit_pointer(&mut self) {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write),
            Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write),
            _ => (),
        }
    }

    pub fn length(&mut self) -> u32 {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.length(),
            Some((Entry::Directory(dir), _)) => dir.length(),
            _ => 0,
        }
    }

    pub fn commit_length(&mut self) {
        match &mut self.entry {
            Some((Entry::File(file), _)) => file.set_length(self.length_write),
            _ => (),
        }
    }

    pub fn flush(&mut self) {
        if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
            let _ = buffered_file;
        }
    }
}

impl Drop for FileDevice {
    fn drop(&mut self) {
        self.flush();
    }
}

impl Device for FileDevice {
    fn read(&mut self, port: u8) -> u8 {
        match port {
            0x0 => read_b!(self.entry.is_some()),
            0x1 => read_b!(self.success),
            0x2 => self.path_buffer.read(),
            0x3 => read_b!(self.entry_type()),
            0x4 => self.read_byte(),
            0x5 => self.read_byte(),
            0x6 => self.read_child_path(),
            0x7 => read_b!(self.child_type()),
            0x8 => read_hh!(self.pointer()),
            0x9 => read_hl!(self.pointer()),
            0xa => read_lh!(self.pointer()),
            0xb => read_ll!(self.pointer()),
            0xc => read_hh!(self.length()),
            0xd => read_hl!(self.length()),
            0xe => read_lh!(self.length()),
            0xf => read_ll!(self.length()),
            _ => unreachable!(),
        }
    }

    fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
        match port {
            0x0 => self.write_to_entry_port(value),
            0x1 => self.write_to_action_port(value),
            0x2 => self.path_buffer.set_pointer(value),
            0x3 => self.ascend_to_parent(),
            0x4 => self.write_byte(value),
            0x5 => self.write_byte(value),
            0x6 => self.set_child_path(value),
            0x7 => self.descend_to_child(),
            0x8 => write_hh!(self.pointer_write, value),
            0x9 => write_hl!(self.pointer_write, value),
            0xa => write_lh!(self.pointer_write, value),
            0xb => {write_ll!(self.pointer_write, value); self.commit_pointer()},
            0xc => write_hh!(self.length_write, value),
            0xd => write_hl!(self.length_write, value),
            0xe => write_lh!(self.length_write, value),
            0xf => {write_ll!(self.length_write, value); self.commit_length()},
            _ => unreachable!(),
        };
        return None;
    }

    fn wake(&mut self) -> bool {
        false
    }
}