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();
}
}