summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-01-31 07:38:50 +1300
committerBen Bridle <bridle.benjamin@gmail.com>2024-01-31 07:39:07 +1300
commit28101de56231252ca0cfa6a9f107b75112c9acad (patch)
treef5c82a6894562bfb8ed8ab94e9345cefaa6fb96b
parent30d2f099c9edf4f59fbbdd6686988ae7b0622ba2 (diff)
downloadbedrock-pc-28101de56231252ca0cfa6a9f107b75112c9acad.zip
Implement new file device interface
This is a complete redesign of the file device. The most notable addition is the ability to ascend and descend the file tree.
-rw-r--r--src/devices.rs64
-rw-r--r--src/devices/file.rs293
-rw-r--r--src/devices/file/buffered_file.rs127
-rw-r--r--src/devices/file/circular_path_buffer.rs66
-rw-r--r--src/devices/file/directory_entry.rs30
-rw-r--r--src/devices/file/directory_listing.rs108
-rw-r--r--src/devices/file/entry.rs36
-rw-r--r--src/devices/file/operations.rs43
-rw-r--r--src/main.rs2
9 files changed, 674 insertions, 95 deletions
diff --git a/src/devices.rs b/src/devices.rs
index 5b274c9..e1a00f7 100644
--- a/src/devices.rs
+++ b/src/devices.rs
@@ -182,22 +182,22 @@ impl DeviceBus for StandardDevices {
// Stream
// File
- 0xA0 => read_b!(self.file.file.is_some()),
- 0xA1 => read_b!(self.file.operation_state),
- 0xA2 => no_read!(),
- 0xA3 => no_read!(),
- 0xA4 => read_hh!(self.file.pointer),
- 0xA5 => read_hl!(self.file.pointer),
- 0xA6 => read_lh!(self.file.pointer),
- 0xA7 => read_ll!(self.file.pointer),
- 0xA8 => self.file.read(),
- 0xA9 => self.file.read(),
- 0xAA => no_read!(),
- 0xAB => no_read!(),
- 0xAC => read_hh!(self.file.file_size),
- 0xAD => read_hl!(self.file.file_size),
- 0xAE => read_lh!(self.file.file_size),
- 0xAF => read_ll!(self.file.file_size),
+ 0xA0 => read_b!(self.file.entry.is_some()),
+ 0xA1 => read_b!(self.file.move_success),
+ 0xA2 => self.file.name_buffer.read_byte(),
+ 0xA3 => read_b!(self.file.entry_type()),
+ 0xA4 => self.file.read_byte(),
+ 0xA5 => self.file.read_byte(),
+ 0xA6 => self.file.read_child_name(),
+ 0xA7 => read_b!(self.file.child_type()),
+ 0xA8 => read_hh!(self.file.pointer()),
+ 0xA9 => read_hl!(self.file.pointer()),
+ 0xAA => read_lh!(self.file.pointer()),
+ 0xAB => read_ll!(self.file.pointer()),
+ 0xAC => read_hh!(self.file.length()),
+ 0xAD => read_hl!(self.file.length()),
+ 0xAE => read_lh!(self.file.length()),
+ 0xAF => read_ll!(self.file.length()),
_ => unimplemented!("Reading from device port 0x{port:02x}"),
}
@@ -310,22 +310,22 @@ impl DeviceBus for StandardDevices {
0x8E => no_write!(),
0x8F => no_write!(),
// File
- 0xA0 => self.file.push_name_byte(val),
- 0xA1 => no_write!(),
- 0xA2 => no_write!(),
- 0xA3 => no_write!(),
- 0xA4 => write_hh!(self.file.pointer),
- 0xA5 => write_hl!(self.file.pointer),
- 0xA6 => write_lh!(self.file.pointer),
- 0xA7 => write_ll!(self.file.pointer),
- 0xA8 => no_write!(),
- 0xA9 => no_write!(),
- 0xAA => no_write!(),
- 0xAB => no_write!(),
- 0xAC => write_hh!(self.file.file_size),
- 0xAD => write_hl!(self.file.file_size),
- 0xAE => write_lh!(self.file.file_size),
- 0xAF => { write_ll!(self.file.file_size); self.file.set_file_size() },
+ 0xA0 => self.file.write_to_open_port(val),
+ 0xA1 => self.file.write_to_move_port(val),
+ 0xA2 => self.file.set_name_pointer(val),
+ 0xA3 => self.file.ascend_to_parent(),
+ 0xA4 => self.file.write_byte(val),
+ 0xA5 => self.file.write_byte(val),
+ 0xA6 => self.file.set_child_name_pointer(val),
+ 0xA7 => self.file.descend_to_child(),
+ 0xA8 => write_hh!(self.file.new_pointer),
+ 0xA9 => write_hl!(self.file.new_pointer),
+ 0xAA => write_lh!(self.file.new_pointer),
+ 0xAB => { write_ll!(self.file.new_pointer); self.file.commit_pointer() },
+ 0xAC => write_hh!(self.file.new_length),
+ 0xAD => write_hl!(self.file.new_length),
+ 0xAE => write_lh!(self.file.new_length),
+ 0xAF => { write_ll!(self.file.new_length); self.file.commit_length() },
// Bytestreams
0x96 => self.stream.write_stdout(val),
diff --git a/src/devices/file.rs b/src/devices/file.rs
index f442dd8..163c10d 100644
--- a/src/devices/file.rs
+++ b/src/devices/file.rs
@@ -1,90 +1,257 @@
-use std::io::{ErrorKind, Read, Seek, SeekFrom};
-use std::fs::{File, OpenOptions};
+mod buffered_file;
+mod circular_path_buffer;
+mod directory_entry;
+mod directory_listing;
+mod entry;
+mod operations;
+
+pub use buffered_file::*;
+pub use circular_path_buffer::*;
+pub use directory_entry::*;
+pub use directory_listing::*;
+pub use entry::*;
+use operations::*;
+
+use std::fs::{OpenOptions, metadata};
+use std::os::unix::ffi::OsStrExt;
+use std::path::{Component, Path, PathBuf};
+
+fn is_blank_path(path: &Path) -> bool {
+ path == PathBuf::new()
+}
pub struct FileDevice {
- pub name: Vec<u8>,
- pub rename: Vec<u8>,
+ /// The path to which the file device is confined. Files and directories
+ /// outside of this directory cannot be accessed.
+ pub base_path: PathBuf,
- pub file: Option<File>,
- pub operation_state: bool,
- pub pointer: u32,
- pub file_size: u32,
+ pub open_buffer: CircularPathBuffer,
+ pub move_buffer: CircularPathBuffer,
+ pub name_buffer: CircularPathBuffer,
+
+ pub entry: Option<(Entry, PathBuf)>,
+
+ pub move_success: bool,
+ pub new_pointer: u32,
+ pub new_length: u32,
}
impl FileDevice {
pub fn new() -> Self {
Self {
- name: Vec::new(),
- rename: Vec::new(),
-
- file: None,
- operation_state: false,
- pointer: 0,
- file_size: 0,
- }
- }
-
- pub fn push_name_byte(&mut self, byte: u8) {
- if byte != 0 { self.name.push(byte); return }
- // If char was null, attempt to open the file and read file size.
- let path: std::ffi::OsString = std::os::unix::ffi::OsStringExt::from_vec(
- std::mem::take(&mut self.name));
- let try_open = OpenOptions::new().read(true).write(true).open(path);
- if let Ok(file) = try_open {
- if let Ok(metadata) = file.metadata() {
- // Success.
- self.file = Some(file);
- match u32::try_from(metadata.len()) {
- Ok(len) => self.file_size = len,
- Err(_) => self.file_size = u32::MAX,
- }
- return;
+ base_path: PathBuf::from("/home/ben/Sandbox"),
+
+ open_buffer: CircularPathBuffer::new(),
+ move_buffer: CircularPathBuffer::new(),
+ name_buffer: CircularPathBuffer::new(),
+
+ entry: None,
+
+ move_success: false,
+ new_pointer: 0,
+ new_length: 0,
+ }
+ }
+
+ pub fn close_entry(&mut self) {
+ self.open_buffer.clear();
+ self.move_buffer.clear();
+ self.name_buffer.clear();
+ self.entry = None;
+ self.new_pointer = 0;
+ self.new_length = 0;
+ }
+
+ pub fn write_to_open_port(&mut self, byte: u8) {
+ if let Some(relative_path) = self.open_buffer.push_byte(byte) {
+ println!("Attempting to open path {relative_path:?}");
+ self.close_entry();
+ if !is_blank_path(&relative_path) {
+ if let Ok(path) = self.attach_base(&relative_path) {
+ println!("Attempting to open absolute path {path:?}");
+ let _ = self.open_entry(&path);
+ };
}
}
- // Failure.
- self.close();
}
- pub fn set_file_size(&mut self) {
- if let Some(file) = &self.file {
- if let Ok(()) = file.set_len(self.file_size.into()) {
- return;
+ pub fn set_name_pointer(&mut self, byte: u8) {
+ self.name_buffer.set_pointer(byte);
+ }
+
+ /// Open the entry at the given path.
+ pub fn open_entry(&mut self, path: &Path) -> Result<(), ()> {
+ macro_rules! raise_on_err {
+ ($res:expr) => {match $res {Ok(v)=>v, Err(_)=>return Err(())} } }
+
+ if !path.starts_with(&self.base_path) { return Err(()); }
+ let metadata = raise_on_err!(metadata(&path));
+ if metadata.is_file() {
+ println!("Opening file as readable/writable");
+ if let Ok(file) = OpenOptions::new().read(true).write(true).open(&path) {
+ self.close_entry();
+ let file_entry = Entry::File(BufferedFile::new(file));
+ self.entry = Some((file_entry, path.to_owned()));
+ let relative = remove_base(&path, &self.base_path).unwrap();
+ self.name_buffer.populate(relative.as_os_str().as_bytes());
+ println!("Success, opened file {path:?}");
+ return Ok(());
};
- }
- self.close();
+ } else if metadata.is_dir() {
+ println!("Opening directory");
+ if let Ok(listing) = DirectoryListing::from_path(&path, &self.base_path) {
+ let dir_entry = Entry::Directory(listing);
+ self.entry = Some((dir_entry, path.to_owned()));
+ let relative = remove_base(&path, &self.base_path).unwrap();
+ self.name_buffer.populate(relative.as_os_str().as_bytes());
+ println!("Success, opened directory {path:?}");
+ return Ok(());
+ };
+ };
+ return Err(());
}
- pub fn read(&mut self) -> u8 {
- if let Some(file) = &mut self.file {
- let mut buffer: [u8; 1] = [0; 1];
- match file.read_exact(&mut buffer) {
- Ok(()) => return buffer[0],
- Err(err) => match err.kind() {
- ErrorKind::UnexpectedEof => return 0,
- _ => unimplemented!("File read error: ErrorKind::{:?}", err.kind()),
+ pub fn write_to_move_port(&mut self, byte: u8) {
+ if let Some(dest) = self.move_buffer.push_byte(byte) {
+ if let Some((_, source)) = &self.entry {
+ if is_blank_path(&dest) {
+ self.move_success = delete_entry(&source);
+ } else if let Ok(destination) = self.attach_base(&dest) {
+ println!("Attempting to move entry: {destination:?}");
+ self.move_success = move_entry(&source, &destination);
+ }
+ } else {
+ if is_blank_path(&dest) {
+ self.move_success = false;
+ } else if let Ok(destination) = self.attach_base(&dest) {
+ println!("Attempting to create entry: {destination:?}");
+ self.move_success = create_file(&destination);
}
}
+ self.close_entry();
+ }
+ }
+
+ /// Attempt to open the parent directory of the current entry.
+ pub fn ascend_to_parent(&mut self) {
+ if let Some((_, path)) = &self.entry {
+ if let Some(parent_path) = path.parent() {
+ let path = parent_path.to_owned();
+ let _ = self.open_entry(&path);
+ }
}
- self.close();
- return 0;
}
- pub fn seek(&mut self) {
- if let Some(file) = &mut self.file {
- let point = SeekFrom::Start(self.pointer.into());
- if let Ok(pointer) = file.seek(point) {
- if let Ok(pointer_u32) = pointer.try_into() {
- self.pointer = pointer_u32;
- return;
+ /// Attempt to open the currently-selected child.
+ pub fn descend_to_child(&mut self) {
+ if let Some((Entry::Directory(listing), _)) = &self.entry {
+ if let Some(child_path) = listing.child_path() {
+ if let Ok(child_path) = self.attach_base(&child_path) {
+ let child_path = child_path.to_owned();
+ let _ = self.open_entry(&child_path);
}
};
}
- self.close();
}
- pub fn close(&mut self) {
- self.file = None;
- self.pointer = 0;
- self.file_size = 0;
+ /// Return true if the currently-open entry is a directory.
+ pub fn entry_type(&self) -> bool {
+ match self.entry {
+ Some((Entry::Directory(_), _)) => true,
+ _ => false,
+ }
+ }
+
+ /// Return true if the currently-selected child is a directory.
+ 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);
+ }
+ }
+
+ /// Return 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
+ }
+ }
+
+ pub fn read_byte(&mut self) -> u8 {
+ match &mut self.entry {
+ Some((Entry::File(buffered_file), _)) => buffered_file.read_byte(),
+ _ => 0,
+ }
+ }
+
+ 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 = std::mem::take(&mut self.new_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 = std::mem::take(&mut self.new_length);
+ match &mut self.entry {
+ Some((Entry::File(buffered_file), _)) => buffered_file.set_length(length),
+ _ => (),
+ }
+ }
+
+ fn attach_base(&self, relative_path: &Path) -> Result<PathBuf, ()> {
+ let mut full_path = self.base_path.clone();
+ let mut has_root = false;
+ for component in relative_path.components() {
+ match component {
+ Component::Normal(s) => full_path.push(s),
+ Component::ParentDir => return Err(()),
+ Component::CurDir => continue,
+ Component::RootDir => has_root = true,
+ Component::Prefix(_) => continue,
+ }
+ };
+ match has_root {
+ true => Ok(full_path),
+ false => Err(())
+ }
}
}
diff --git a/src/devices/file/buffered_file.rs b/src/devices/file/buffered_file.rs
new file mode 100644
index 0000000..091b5d9
--- /dev/null
+++ b/src/devices/file/buffered_file.rs
@@ -0,0 +1,127 @@
+use std::fs::File;
+use std::io::{BufReader, BufWriter};
+use std::io::{Read, Write};
+use std::io::{ErrorKind, Seek, SeekFrom};
+
+enum AccessMode {
+ Read(BufReader<File>),
+ Write(BufWriter<File>),
+ None,
+}
+
+impl AccessMode {
+ pub fn unwrap(self) -> File {
+ match self {
+ Self::Read(reader) => reader.into_inner(),
+ Self::Write(writer) => writer.into_inner().unwrap(),
+ Self::None => unreachable!(),
+ }
+ }
+}
+
+impl Default for AccessMode {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
+pub struct BufferedFile {
+ file: AccessMode,
+}
+
+impl BufferedFile {
+ pub fn new(file: File) -> Self {
+ Self {
+ file: AccessMode::Read(BufReader::new(file)),
+ }
+ }
+
+ pub fn read_byte(&mut self) -> u8 {
+ let mut buffer = [0u8; 1];
+
+ let read_result = match &mut self.file {
+ AccessMode::Read(reader) => reader.read_exact(&mut buffer),
+ AccessMode::Write(writer) => {
+ let address = writer.stream_position().unwrap();
+ let file = std::mem::take(&mut self.file).unwrap();
+ let mut reader = BufReader::new(file);
+ reader.seek(SeekFrom::Start(address)).unwrap();
+ let read_result = reader.read_exact(&mut buffer);
+ self.file = AccessMode::Read(reader);
+ read_result
+ }
+ AccessMode::None => unreachable!(),
+ };
+
+ match read_result {
+ Ok(_) => buffer[0],
+ Err(error) => match error.kind() {
+ ErrorKind::UnexpectedEof => 0,
+ _ => panic!("{error:?}"),
+ }
+ }
+ }
+
+ pub fn write_byte(&mut self, byte: u8) {
+ let mut buffer = [byte; 1];
+
+ let write_result = match &mut self.file {
+ AccessMode::Write(writer) => writer.write_all(&mut buffer),
+ AccessMode::Read(reader) => {
+ let address = reader.stream_position().unwrap();
+ let file = std::mem::take(&mut self.file).unwrap();
+ let mut writer = BufWriter::new(file);
+ writer.seek(SeekFrom::Start(address)).unwrap();
+ let write_result = writer.write_all(&mut buffer);
+ self.file = AccessMode::Write(writer);
+ write_result
+ }
+ AccessMode::None => unreachable!(),
+ };
+
+ write_result.unwrap();
+ }
+
+ pub fn pointer(&mut self) -> u32 {
+ let position = match &mut self.file {
+ AccessMode::Read(reader) => reader.stream_position(),
+ AccessMode::Write(writer) => writer.stream_position(),
+ AccessMode::None => unreachable!(),
+ };
+ u32::try_from(position.unwrap()).unwrap_or(u32::MAX)
+ }
+
+ pub fn set_pointer(&mut self, pointer: u32) {
+ let position = SeekFrom::Start(pointer as u64);
+ match &mut self.file {
+ AccessMode::Read(reader) => reader.seek(position).unwrap(),
+ AccessMode::Write(writer) => writer.seek(position).unwrap(),
+ AccessMode::None => unreachable!(),
+ };
+ }
+
+ pub fn length(&mut self) -> u32 {
+ let length = match &mut self.file {
+ AccessMode::Read(reader) => reader.stream_len(),
+ AccessMode::Write(writer) => writer.stream_len(),
+ AccessMode::None => unreachable!(),
+ };
+ u32::try_from(length.unwrap()).unwrap_or(u32::MAX)
+ }
+
+ pub fn set_length(&mut self, length: u32) {
+ match &mut self.file {
+ AccessMode::Read(_) => {
+ let file = std::mem::take(&mut self.file).unwrap();
+ file.set_len(length as u64).unwrap();
+ self.file = AccessMode::Read(BufReader::new(file));
+ }
+ AccessMode::Write(_) => {
+ let file = std::mem::take(&mut self.file).unwrap();
+ file.set_len(length as u64).unwrap();
+ self.file = AccessMode::Read(BufReader::new(file));
+ }
+ AccessMode::None => unreachable!(),
+ };
+ }
+}
diff --git a/src/devices/file/circular_path_buffer.rs b/src/devices/file/circular_path_buffer.rs
new file mode 100644
index 0000000..f828f73
--- /dev/null
+++ b/src/devices/file/circular_path_buffer.rs
@@ -0,0 +1,66 @@
+use std::ffi::OsString;
+use std::os::unix::ffi::OsStringExt;
+use std::path::PathBuf;
+
+pub struct CircularPathBuffer {
+ buffer: [u8; 256],
+ pointer: u8,
+}
+
+impl CircularPathBuffer {
+ pub fn new() -> Self {
+ Self { buffer: [0; 256] , pointer: 0 }
+ }
+
+ pub fn clear(&mut self) {
+ self.buffer.fill(0);
+ self.pointer = 0;
+ }
+
+ pub fn populate(&mut self, bytes: &[u8]) {
+ self.clear();
+ if bytes.len() > 255 {
+ unreachable!(
+ "Attempted to populate CircularPathBuffer with {} bytes: {:?}",
+ bytes.len(), String::from_utf8_lossy(bytes)
+ )
+ }
+ let self_slice = &mut self.buffer[..bytes.len()];
+ self_slice.copy_from_slice(&bytes);
+ }
+
+ pub fn set_pointer(&mut self, value: u8) {
+ self.pointer = 0;
+ if value != 0x00 {
+ for (i, c) in self.buffer.iter().enumerate() {
+ match c {
+ b'/' => self.pointer = (i + 1) as u8,
+ 0x00 => break,
+ _ => continue,
+ }
+ }
+ }
+ }
+
+ pub fn read_byte(&mut self) -> u8 {
+ let pointer = self.pointer as usize;
+ self.pointer = self.pointer.wrapping_add(1);
+ self.buffer[pointer]
+ }
+
+ // Returns an unsanitized relative path.
+ pub fn push_byte(&mut self, value: u8) -> Option<PathBuf> {
+ if value == 0x00 {
+ let pointer = self.pointer as usize;
+ let vec = self.buffer[..pointer].to_vec();
+ self.clear();
+ let os_string: OsString = OsStringExt::from_vec(vec);
+ Some(os_string.into())
+ } else {
+ let pointer = self.pointer as usize;
+ self.pointer = self.pointer.wrapping_add(1);
+ self.buffer[pointer] = value;
+ None
+ }
+ }
+}
diff --git a/src/devices/file/directory_entry.rs b/src/devices/file/directory_entry.rs
new file mode 100644
index 0000000..45de817
--- /dev/null
+++ b/src/devices/file/directory_entry.rs
@@ -0,0 +1,30 @@
+use crate::*;
+
+use std::cmp::Ordering;
+
+#[derive(PartialEq, Eq)]
+pub struct DirectoryChild {
+ pub byte_path: Vec<u8>,
+ pub entry_type: EntryType,
+}
+
+impl PartialOrd for DirectoryChild {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ let entry_type_ord = self.entry_type.cmp(&other.entry_type);
+ let final_order = match entry_type_ord {
+ Ordering::Equal => self.byte_path.cmp(&other.byte_path),
+ _ => entry_type_ord,
+ };
+ Some(final_order)
+ }
+}
+
+impl Ord for DirectoryChild {
+ fn cmp(&self, other: &Self) -> Ordering {
+ let entry_type_ord = self.entry_type.cmp(&other.entry_type);
+ match entry_type_ord {
+ Ordering::Equal => self.byte_path.cmp(&other.byte_path),
+ _ => entry_type_ord,
+ }
+ }
+}
diff --git a/src/devices/file/directory_listing.rs b/src/devices/file/directory_listing.rs
new file mode 100644
index 0000000..1f94a3a
--- /dev/null
+++ b/src/devices/file/directory_listing.rs
@@ -0,0 +1,108 @@
+use crate::*;
+
+use std::ffi::OsString;
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+use std::path::{Component, Path, PathBuf};
+
+
+pub struct DirectoryListing {
+ children: Vec<DirectoryChild>,
+ length: u32,
+ selected: u32,
+ name_buffer: CircularPathBuffer,
+}
+
+impl DirectoryListing {
+ pub fn from_path(path: &Path, base: &Path) -> Result<Self, ()> {
+ macro_rules! continue_on_err {
+ ($result:expr) => { match $result { Ok(v) => v, Err(_) => continue} };
+ }
+
+ let mut children = Vec::new();
+ if let Ok(iter_dir) = std::fs::read_dir(path) {
+ for (i, entry_result) in iter_dir.enumerate() {
+ if i == u16::MAX as usize {
+ println!("Warning, this directory contains more than 65536 entries.")
+ };
+ let entry = continue_on_err!(entry_result);
+ let path = continue_on_err!(remove_base(&entry.path(), &base));
+ let byte_path = path.as_os_str().as_bytes();
+ if byte_path.len() > 255 {
+ continue;
+ };
+ let metadata = continue_on_err!(std::fs::metadata(&entry.path()));
+ children.push( DirectoryChild {
+ byte_path: Vec::from(byte_path),
+ entry_type: if metadata.is_file() {
+ EntryType::File
+ } else if metadata.is_dir() {
+ EntryType::Directory
+ } else {
+ unreachable!();
+ },
+ } )
+ }
+ children.sort_unstable();
+ let length = u32::try_from(children.len()).unwrap_or(u32::MAX);
+ let selected = 0;
+ let name_buffer = CircularPathBuffer::new();
+ Ok(Self { children, length, selected, name_buffer } )
+ } else {
+ Err(())
+ }
+ }
+
+ pub fn get(&self, index: u32) -> Option<&DirectoryChild> {
+ self.children.get(index as usize)
+ }
+
+ pub fn length(&self) -> u32 {
+ self.length
+ }
+
+ pub fn selected(&self) -> u32 {
+ self.selected
+ }
+
+ pub fn set_selected(&mut self, index: u32) {
+ if let Some(info) = self.get(index) {
+ self.name_buffer.populate(&info.byte_path.clone());
+ self.selected = index;
+ } else {
+ self.name_buffer.clear();
+ self.selected = 0;
+ }
+ }
+
+ pub fn child_name(&mut self) -> &mut CircularPathBuffer {
+ &mut self.name_buffer
+ }
+
+ pub fn child_type(&self) -> Option<EntryType> {
+ self.get(self.selected).and_then(|i| Some(i.entry_type))
+ }
+
+ pub fn child_path(&self) -> Option<PathBuf> {
+ self.get(self.selected).and_then(|i| {
+ let os_string: OsString = OsStringExt::from_vec(i.byte_path.clone());
+ Some(os_string.into())
+ })
+ }
+}
+
+pub fn remove_base(absolute_path: &Path, base_path: &Path) -> Result<PathBuf, ()> {
+ if let Ok(relative) = absolute_path.strip_prefix(base_path) {
+ let mut baseless_path = PathBuf::from("/");
+ for component in relative.components() {
+ match component {
+ Component::Normal(s) => baseless_path.push(s),
+ Component::ParentDir => return Err(()),
+ Component::CurDir => continue,
+ Component::RootDir => continue,
+ Component::Prefix(_) => continue,
+ }
+ }
+ return Ok(baseless_path);
+ }
+ return Err(());
+}
diff --git a/src/devices/file/entry.rs b/src/devices/file/entry.rs
new file mode 100644
index 0000000..a91ae82
--- /dev/null
+++ b/src/devices/file/entry.rs
@@ -0,0 +1,36 @@
+use crate::*;
+
+use std::cmp::Ordering;
+
+pub enum Entry {
+ File(BufferedFile),
+ Directory(DirectoryListing),
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum EntryType {
+ File,
+ Directory,
+}
+
+impl PartialOrd for EntryType {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ match (self, other) {
+ (EntryType::Directory, EntryType::Directory) => Some(Ordering::Equal ),
+ (EntryType::Directory, EntryType::File ) => Some(Ordering::Less ),
+ (EntryType::File, EntryType::Directory) => Some(Ordering::Greater),
+ (EntryType::File, EntryType::File ) => Some(Ordering::Equal ),
+ }
+ }
+}
+
+impl Ord for EntryType {
+ fn cmp(&self, other: &Self) -> Ordering {
+ match (self, other) {
+ (EntryType::Directory, EntryType::Directory) => Ordering::Equal ,
+ (EntryType::Directory, EntryType::File ) => Ordering::Less ,
+ (EntryType::File, EntryType::Directory) => Ordering::Greater,
+ (EntryType::File, EntryType::File ) => Ordering::Equal ,
+ }
+ }
+}
diff --git a/src/devices/file/operations.rs b/src/devices/file/operations.rs
new file mode 100644
index 0000000..f33509b
--- /dev/null
+++ b/src/devices/file/operations.rs
@@ -0,0 +1,43 @@
+use std::io::ErrorKind;
+use std::path::Path;
+
+pub fn entry_exists(source: &Path) -> bool {
+ std::fs::metadata(source).is_ok()
+}
+
+// Delete a file or folder, returning true if successful.
+pub fn delete_entry(source: &Path) -> bool {
+ match std::fs::remove_file(source) {
+ Ok(_) => true,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => true,
+ ErrorKind::IsADirectory => match std::fs::remove_dir_all(source) {
+ Ok(_) => true,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => true,
+ _ => false,
+ }
+ }
+ _ => false,
+ }
+ }
+}
+
+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()
+}
+
+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()
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 98bc20f..80dcb54 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,7 @@
#![feature(bigint_helper_methods)]
+#![feature(io_error_more)]
#![feature(split_array)]
+#![feature(seek_stream_len)]
use std::io::Read;
use std::process::exit;