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(dir), path)) = std::mem::take(&mut self.entry) { 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 { 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), 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), _ => unreachable!(), }; return None; } fn wake(&mut self) -> bool { false } }