diff options
Diffstat (limited to 'src/devices/file_device.rs')
-rw-r--r-- | src/devices/file_device.rs | 300 |
1 files changed, 187 insertions, 113 deletions
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs index 61966b1..83f0a56 100644 --- a/src/devices/file_device.rs +++ b/src/devices/file_device.rs @@ -1,20 +1,4 @@ -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}; +use crate::*; pub struct FileDevice { @@ -25,13 +9,14 @@ pub struct FileDevice { pub action_buffer: BedrockPathBuffer, pub path_buffer: BedrockPathBuffer, - pub entry: Option<(Entry, BedrockFilePath)>, - pub cached_dir: Option<(Entry, BedrockFilePath)>, + pub entry: Option<(Entry, BedrockFilePath, Instant)>, + pub cached_dir: Option<(Entry, BedrockFilePath, Instant)>, - pub success: bool, + pub error: bool, pub pointer_write: u32, pub length_write: u32, + pub enable: bool, pub enable_read: bool, pub enable_write: bool, pub enable_create: bool, @@ -39,21 +24,95 @@ pub struct FileDevice { pub enable_delete: bool, } + +impl Device for FileDevice { + fn read(&mut self, port: u8) -> u8 { + if !self.enable { return 0x00; } + match port { + 0x0 => read_b!(self.entry.is_some()), + 0x1 => read_b!(std::mem::take(&mut self.error)), + 0x2 => self.read_byte(), + 0x3 => self.read_byte(), + 0x4 => self.path_buffer.read(), + 0x5 => read_b!(self.entry_type()), + 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> { + if !self.enable { return None; } + match port { + 0x0 => self.write_to_entry_port(value), + 0x1 => self.write_to_action_port(value), + 0x2 => self.write_byte(value), + 0x3 => self.write_byte(value), + 0x4 => self.path_buffer.set_pointer(value), + 0x5 => self.ascend_to_parent(), + 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 + } + + fn reset(&mut self) { + todo!() + } +} + + impl FileDevice { - pub fn new() -> Self { + pub fn new(config: &EmulatorConfig) -> Self { #[cfg(target_family = "unix")] let default_base: PathBuf = PathBuf::from("/"); #[cfg(target_family = "windows")] let default_base: PathBuf = PathBuf::from(""); + let current_dir = match std::env::current_dir() { + Ok(dir) => PathBuf::from(dir), + Err(_) => PathBuf::from(""), + }; + + let (enable, base_path, default_path) = if config.trust_files { + (true, default_base, current_dir) + } else if let Some(config_dir) = dirs_next::config_dir() { + let bedrock_dir = config_dir.join("bedrock"); + let identifier = config.identifier.clone().unwrap_or("default".to_string()); + let sandbox_dir = bedrock_dir.join(identifier); + vagabond::make_directory(&sandbox_dir).unwrap(); + (true, sandbox_dir.clone(), sandbox_dir) + } else { + error!("Could not determine sandbox path for file device"); + (false, default_base, current_dir) + }; + // 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(""), - }, + base_path, + default_path, entry_buffer: BedrockPathBuffer::new(), action_buffer: BedrockPathBuffer::new(), @@ -62,10 +121,11 @@ impl FileDevice { entry: None, cached_dir: None, - success: false, + error: false, pointer_write: 0, length_write: 0, + enable, enable_read: true, enable_write: true, enable_create: true, @@ -74,6 +134,12 @@ impl FileDevice { } } + pub fn check_success(&mut self, success: bool) { + if !success { + self.error = true; + } + } + /// Safely close the current entry, cleaning up entry variables. pub fn close(&mut self) { self.entry_buffer.clear(); @@ -81,10 +147,10 @@ impl FileDevice { self.path_buffer.clear(); self.flush(); - if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) { + if let Some((Entry::Directory(mut dir), path, time)) = 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)); + self.cached_dir = Some((Entry::Directory(dir), path, time)); } } @@ -100,17 +166,17 @@ impl FileDevice { if let Ok(file) = open_result { self.close(); self.path_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::File(BufferedFile::new(file)), path)); + self.entry = Some((Entry::File(BufferedFile::new(file)), path, Instant::now())); 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 { + // Attempt to use the cached directory if not too old. + if let Some((dir, cached_path, time)) = std::mem::take(&mut self.cached_dir) { + if cached_path == path && time.elapsed() < Duration::from_secs(1) { self.close(); self.path_buffer.populate(cached_path.as_buffer()); - self.entry = Some((dir, cached_path)); + self.entry = Some((dir, cached_path, time)); return Ok(()); } } @@ -118,7 +184,7 @@ impl FileDevice { if let Some(listing) = DirectoryListing::from_path(&path) { self.close(); self.path_buffer.populate(path.as_buffer()); - self.entry = Some((Entry::Directory(listing), path)); + self.entry = Some((Entry::Directory(listing), path, Instant::now())); return Ok(()); }; } @@ -132,10 +198,16 @@ impl FileDevice { 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, - }; + // Attempt to open file if buffer was not empty. + if buffer[0] != 0 { + let success = match BedrockFilePath::from_buffer(buffer, &self.base_path) { + Some(path) => self.open(path).is_ok(), + None => false, + }; + self.check_success(success); + } else { + self.check_success(true); + } } } @@ -144,22 +216,23 @@ impl FileDevice { 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 let Some((_, source, _)) = &self.entry { if destination_blank { if self.enable_delete { - self.success = delete_entry(&source.as_path()); + self.check_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()); + self.check_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.check_success(create_file(&dest.as_path())); } + } else { + self.check_success(false); } self.close(); } @@ -167,35 +240,38 @@ impl FileDevice { /// 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, + if let Some((_, path, _)) = &self.entry { + let success = match path.parent() { + Some(parent) => self.open(parent).is_ok(), + None => false, }; + self.check_success(success); } else { - match BedrockFilePath::from_path(&self.default_path, &self.base_path) { - Some(default) => self.success = self.open(default).is_ok(), - None => self.success = false, + let success = match BedrockFilePath::from_path(&self.default_path, &self.base_path) { + Some(default) => self.open(default).is_ok(), + None => false, }; + self.check_success(success); } } /// 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, + if let Some((Entry::Directory(dir), _, _)) = &self.entry { + let success = match dir.child_path() { + Some(child) => self.open(child).is_ok(), + None => false, }; + self.check_success(success); } else { - self.success = false; + self.check_success(false); } } /// Return true if the current entry is a directory. pub fn entry_type(&self) -> bool { match self.entry { - Some((Entry::Directory(_), _)) => true, + Some((Entry::Directory(_), _, _)) => true, _ => false, } } @@ -203,13 +279,13 @@ impl FileDevice { /// 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(), + 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 { + if let Some((Entry::Directory(dir), _, _)) = &mut self.entry { dir.child_path_buffer().set_pointer(byte); } } @@ -217,7 +293,7 @@ impl FileDevice { /// 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((Entry::Directory(dir), _, _)) => match dir.child_type() { Some(EntryType::Directory) => true, _ => false, } @@ -228,7 +304,7 @@ impl FileDevice { /// Read a byte from the current file. pub fn read_byte(&mut self) -> u8 { match &mut self.entry { - Some((Entry::File(file), _)) => file.read(), + Some((Entry::File(file), _, _)) => file.read(), _ => 0, } } @@ -236,102 +312,100 @@ impl FileDevice { /// 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), + 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(), + 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), + 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(), + 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), + 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 { + 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!(), + +/// Create a new file if it doesn't already exist, returning true if successful. +pub fn create_file(destination: &Path) -> bool { + if entry_exists(destination) { + false + } else { + if let Some(parent_path) = destination.parent() { + let _ = std::fs::create_dir_all(parent_path); } + std::fs::OpenOptions::new().write(true).create_new(true) + .open(destination).is_ok() } +} - 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; +/// Move an entry from one location to another, returning true if successful. +pub fn move_entry(source: &Path, destination: &Path) -> bool { + if !entry_exists(source) || entry_exists(destination) { + return false; } + std::fs::rename(source, destination).is_ok() +} - fn wake(&mut self) -> bool { - false +/// Delete an entry, returning true if successful. +pub fn delete_entry(source: &Path) -> bool { + use std::fs::{remove_file, remove_dir_all}; + use std::io::ErrorKind; + + match remove_file(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + ErrorKind::IsADirectory => match remove_dir_all(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + _ => false, + } + } + _ => false, + } } } + +/// Returns true if an entry already exists at the given path. +fn entry_exists(source: &Path) -> bool { + std::fs::metadata(source).is_ok() +} |