summaryrefslogtreecommitdiff
path: root/src/devices/file
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/file')
-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
6 files changed, 410 insertions, 0 deletions
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()
+ }
+}