diff options
Diffstat (limited to 'src/devices/file_device.rs')
-rw-r--r-- | src/devices/file_device.rs | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs new file mode 100644 index 0000000..4859053 --- /dev/null +++ b/src/devices/file_device.rs @@ -0,0 +1,335 @@ +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 { + buffered_file.flush(); + } + } +} + +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), + 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 + } +} |