summaryrefslogblamecommitdiff
path: root/src/devices/file.rs
blob: 0bea2f43767818502ae9a481654f2d3467a5da7b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                      
                         


                      
                             
                                


                             
                                          
                   
 
 
                       
                           
                              
 


                                        
                                                
                                                     
 

                      
 
                           

                            


                          



                                                       
              
                                    
                                                         
                                            
              
 



                                                   
                             
 

                           
 
                               

                                 

         
                                                             




                                                                        
                                                                           


                                   
                           
                         

                                                                            
     
                                                   
                                                    
                                                                
                               
                                                                                       
             
     
                                                                           













                                                                                    







                                                                              








                                                                             
         
                       
     
                                                    



                                                                                    
                                                    

                                                                       
                     

                                                                                      
                     
                 

                                                                
                 
 




                                                                  
                             
                                                      
                                                                    
             
                

                                                                                                    
         
     
                                                                              
                                        
                             
                                                                   
                                                                   
              
     



                                                                





                                                   
                                                                          












                                                                       
                                                                    










                                                                   
                                                  





                                                                               
                                                 














                                                                                    
                                              














                                                                                        
                                            



                                                                                      
 




                           
mod bedrock_file_path;
mod buffered_file;
mod circular_path_buffer;
mod directory_listing;
mod entry;
mod operations;

pub use bedrock_file_path::*;
pub use buffered_file::*;
pub use circular_path_buffer::*;
pub use directory_listing::*;
pub use entry::*;
use operations::*;

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


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

    pub open_buffer: CircularPathBuffer,
    pub move_buffer: CircularPathBuffer,
    pub name_buffer: CircularPathBuffer,

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

    pub success: bool,
    pub pointer: u32,
    pub length: 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("");

        Self {
            base_path: default_base,
            default_path: match std::env::current_dir() {
                Ok(dir) => PathBuf::from(dir),
                Err(_) => PathBuf::from(""),
            },

            open_buffer: CircularPathBuffer::new(),
            move_buffer: CircularPathBuffer::new(),
            name_buffer: CircularPathBuffer::new(),

            entry: None,
            cached_dir: None,

            success: false,
            pointer: 0,
            length: 0,

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

    /// Commit any pending writes to the currently-open file.
    pub fn flush_entry(&mut self) {
        if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
            buffered_file.flush();
        }
    }

    /// Safely close the currently-open entry, cleaning up entry variables.
    pub fn close_entry(&mut self) {
        self.open_buffer.clear();
        self.move_buffer.clear();
        self.name_buffer.clear();
        self.flush_entry();
        self.pointer = 0;
        self.length = 0;
        if let Some((Entry::Directory(dir), path)) = take(&mut self.entry) {
            self.cached_dir = Some((Entry::Directory(dir), path));
        }
    }

    /// Process a byte received from the OPEN port.
    pub fn write_to_open_port(&mut self, byte: u8) {
        if let Some(buffer) = self.open_buffer.push_byte(byte) {
            self.close_entry();
            if let Some(path) = BedrockFilePath::from_buffer(buffer, &self.base_path) {
                self.success = self.open_entry(path).is_ok();
            }
        }
    }

    /// Opens the entry at the given path.
    pub fn open_entry(&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_entry();
                    self.name_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)) = take(&mut self.cached_dir) {
                    if cached_path == path {
                        self.close_entry();
                        self.name_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_entry();
                    self.name_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(());
    }

    pub fn write_to_move_port(&mut self, byte: u8) {
        if let Some(buffer) = self.move_buffer.push_byte(byte) {
            let blank_destination = buffer[0] == 0x00;
            let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
            self.success = false;

            if let Some((_, source)) = &self.entry {
                if blank_destination {
                    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_entry();
        }
    }

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

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

    pub fn set_name_pointer(&mut self, value: u8) {
        self.name_buffer.set_pointer(value);
    }

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

    /// Reads a byte from the name buffer of the currently-selected child.
    pub fn read_child_name(&mut self) -> u8 {
        if let Some((Entry::Directory(listing), _)) = &mut self.entry {
            listing.child_name().read_byte()
        } else {
            0
        }
    }

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

    /// Returns true if the currently-selected child is a directory.
    pub fn child_type(&self) -> bool {
        if let Some((Entry::Directory(listing), _)) = &self.entry {
            match listing.child_type() {
                Some(EntryType::Directory) => true,
                Some(EntryType::File) => false,
                None => false,
            }
        } else {
            false
        }
    }

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

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

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

    pub fn commit_pointer(&mut self) {
        let pointer = take(&mut self.pointer);
        match &mut self.entry {
            Some((Entry::File(buffered_file), _)) => buffered_file.set_pointer(pointer),
            Some((Entry::Directory(listing), _)) => listing.set_selected(pointer),
            _ => (),
        }
    }

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

    pub fn commit_length(&mut self) {
        let length = take(&mut self.length);
        match &mut self.entry {
            Some((Entry::File(buffered_file), _)) => buffered_file.set_length(length),
            _ => (),
        }
    }
}

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