summaryrefslogtreecommitdiff
path: root/src/devices/file_device
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/file_device')
-rw-r--r--src/devices/file_device/bedrock_file_path.rs287
-rw-r--r--src/devices/file_device/bedrock_path_buffer.rs60
-rw-r--r--src/devices/file_device/buffered_file.rs144
-rw-r--r--src/devices/file_device/directory_listing.rs120
-rw-r--r--src/devices/file_device/entry.rs36
-rw-r--r--src/devices/file_device/operations.rs47
6 files changed, 0 insertions, 694 deletions
diff --git a/src/devices/file_device/bedrock_file_path.rs b/src/devices/file_device/bedrock_file_path.rs
deleted file mode 100644
index fdd8f79..0000000
--- a/src/devices/file_device/bedrock_file_path.rs
+++ /dev/null
@@ -1,287 +0,0 @@
-use super::*;
-
-use std::cmp::Ordering;
-use std::ffi::OsString;
-
-
-#[derive(Clone)]
-pub struct BedrockFilePath {
- /// Sandbox directory
- base: PathBuf,
- /// Path relative to sandbox directory
- relative: PathBuf,
- bytes: Vec<u8>,
- entry_type: Option<EntryType>,
-}
-
-impl BedrockFilePath {
- pub fn from_buffer(buffer: [u8; 256], base: &Path) -> Option<Self> {
- let base = base.to_path_buf();
- let relative = buffer_to_path(buffer)?;
- let bytes = path_to_bytes(&relative)?;
- let entry_type = get_entry_type(base.join(&relative));
- assert_path_is_safe(&relative, &base)?;
- Some(Self { base, relative, bytes, entry_type })
- }
-
- /// Construct an instance from an absolute path and a prefix of that path.
- pub fn from_path(path: &Path, base: &Path) -> Option<Self> {
- let base = base.to_path_buf();
- let relative = path.strip_prefix(&base).ok()?.to_path_buf();
- let bytes = path_to_bytes(&relative)?;
- let entry_type = get_entry_type(base.join(&relative));
- assert_path_is_safe(&relative, &base)?;
- Some( Self { base, relative, bytes, entry_type } )
- }
-
- /// Get the base path used by this path.
- pub fn base(&self) -> &Path {
- &self.base
- }
-
- /// Get this path as a Bedrock-style path, which can be passed to
- /// a Bedrock program.
- pub fn as_bytes(&self) -> &[u8] {
- &self.bytes
- }
-
- /// Get this path as a byte buffer, from which a CircularPathBuffer
- /// can be populated.
- pub fn as_buffer(&self) -> [u8; 256] {
- let mut buffer: [u8; 256] = [0; 256];
- buffer[..self.bytes.len()].copy_from_slice(&self.bytes);
- return buffer;
- }
-
- /// Get this path as an absolute operating-system path, which can
- /// be used to open a file or directory.
- pub fn as_path(&self) -> PathBuf {
- self.base.join(&self.relative)
- }
-
- /// Get the entry type of this path.
- pub fn entry_type(&self) -> Option<EntryType> {
- self.entry_type
- }
-
- /// Get a path which represents the parent of this path.
- pub fn parent(&self) -> Option<Self> {
- #[cfg(target_family = "unix")] {
- Self::from_path(self.as_path().parent()?, &self.base)
- }
- #[cfg(target_family = "windows")] {
- if self.base.components().count() != 0 {
- // Sandboxed path, cannot ascend to a virtual root directory.
- Self::from_path(self.as_path().parent()?, &self.base)
- } else {
- // Unsandboxed path, we can ascend to a virtual root directory.
- match self.as_path().parent() {
- // Ascend to concrete parent directory.
- Some(parent) => Self::from_path(parent, &self.base),
- // Ascend into a virtual root directory.
- None => {
- if self.relative.components().count() != 0 {
- // Ascend from concrete path to virtual root.
- let blank = PathBuf::from("");
- BedrockFilePath::from_path(&blank, &blank)
- } else {
- // Cannot ascend above the virtual root.
- None
- }
- },
- }
- }
- }
- }
-
- /// Returns true if the file would be hidden by the default file browser.
- pub fn is_hidden(&self) -> bool {
- #[cfg(target_family = "unix")] {
- if let Some(stem) = self.relative.file_stem() {
- if let Some(string) = stem.to_str() {
- return string.starts_with('.');
- }
- }
- }
- #[cfg(target_family = "windows")] {
- use std::os::windows::fs::MetadataExt;
- // See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
- // const FILE_ATTRIBUTE_HIDDEN: u32 = 0x00000002;
- // const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x00002000;
- if let Ok(metadata) = std::fs::metadata(self.as_path()) {
- return metadata.file_attributes() & 0x2002 != 0;
- }
- }
- return false;
- }
-}
-
-
-/// Converts the contents of a CircularPathBuffer to a relative path.
-fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> {
- // The buffer must be non-empty and slash-prefixed.
- if bytes[0] != ('/' as u8) {
- return None;
- }
-
- // Find the index of the first null byte.
- let mut null_i = None;
- for (i, b) in bytes.iter().enumerate() {
- if *b == 0x00 {
- null_i = Some(i);
- break;
- }
- }
- // Take a slice, excluding the leading slash, up to the trailing null.
- let slice = &bytes[1..null_i?];
-
- #[cfg(target_family = "unix")] {
- use std::os::unix::ffi::OsStringExt;
- let vec = Vec::from(slice);
- return Some(OsString::from_vec(vec).into())
- }
- #[cfg(target_family = "windows")] {
- use std::os::windows::ffi::OsStringExt;
- let mut string = String::from_utf8_lossy(slice).to_string();
- // Convert drive-current-directory paths to drive-root paths. This is
- // needed because the paths C: and C:/ point to separate directories,
- // but trailing forward-slashes are optional in Bedrock.
- if string.ends_with(':') {
- string.push('/');
- }
- let utf16: Vec<u16> = string.replace(r"/", r"\").encode_utf16().collect();
- return Some(OsString::from_wide(&utf16).into())
- }
-}
-
-/// Convert an operating system path to a Bedrock-style byte path.
-///
-/// A byte path contains at most 255 bytes, and is not null-terminated.
-fn path_to_bytes(path: &Path) -> Option<Vec<u8>> {
- #[cfg(target_family = "unix")]
- let string = path.as_os_str().to_str()?.to_string();
- #[cfg(target_family = "windows")]
- let string = path.as_os_str().to_str()?.replace(r"\", r"/");
-
- // Remove any trailing forward-slash and add a leading forward-slash.
- let mut prefixed_string = String::from("/");
- prefixed_string.push_str(string.trim_end_matches('/'));
- let slice = prefixed_string.as_bytes();
-
- // Error if bytes does not fit into a CircularPathBuffer.
- if slice.len() > 255 { return None; }
-
- Some(Vec::from(slice))
-}
-
-/// Returns true if a relative path can be safely attached to a base without
-/// breaking out of the sandbox.
-fn assert_path_is_safe(relative: &Path, _base: &Path) -> Option<()> {
- #[cfg(target_family = "unix")] {
- // Error if path contains special components.
- for component in relative.components() {
- match component {
- Component::Normal(_) => continue,
- _ => return None,
- }
- }
- }
- #[cfg(target_family = "windows")] {
- // If the base path is empty, the relative path needs to be able to
- // contain the prefix and root element. If the base path is not
- // empty, the relative path must not contain these elements else
- // they will override the base path when joined.
- if _base.components().count() != 0 {
- for component in relative.components() {
- match component {
- Component::Normal(_) => continue,
- _ => return None,
- }
- }
- }
- }
- return Some(());
-}
-
-fn get_entry_type(absolute: PathBuf) -> Option<EntryType> {
- #[cfg(target_family = "windows")] {
- // If path is empty, this is a virtual root directory.
- if absolute.components().count() == 0 {
- return Some(EntryType::Directory)
- }
- }
- let metadata = std::fs::metadata(absolute).ok()?;
- if metadata.is_file() {
- Some(EntryType::File)
- } else if metadata.is_dir() {
- Some(EntryType::Directory)
- } else {
- None
- }
-}
-
-impl std::fmt::Debug for BedrockFilePath {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
- self.as_path().fmt(f)
- }
-}
-
-// ---------------------------------------------------------------------------
-
-impl PartialEq for BedrockFilePath {
- fn eq(&self, other: &Self) -> bool {
- self.bytes == other.bytes && self.entry_type == other.entry_type
- }
-}
-
-impl Eq for BedrockFilePath {}
-
-impl PartialOrd for BedrockFilePath {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for BedrockFilePath {
- fn cmp(&self, other: &Self) -> Ordering {
- match self.entry_type.cmp(&other.entry_type) {
- Ordering::Equal => compare_ascii_slices(&self.bytes, &other.bytes),
- ordering => ordering,
- }
- }
-}
-
-/// Compare two ASCII byte-slices in case-agnostic alphabetic order.
-fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering {
- let l = std::cmp::min(left.len(), right.len());
- let lhs = &left[..l];
- let rhs = &right[..l];
-
- for i in 0..l {
- let a = remap_ascii(lhs[i]);
- let b = remap_ascii(rhs[i]);
- match a.cmp(&b) {
- Ordering::Equal => (),
- non_eq => return non_eq,
- }
- }
-
- left.len().cmp(&right.len())
-}
-
-/// Remap ASCII values so that they sort in case-agnostic alphabetic order:
-///
-/// ```text
-/// !"#$%&'()*+,-./0123456789:;<=>?
-/// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo
-/// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_
-/// ```
-fn remap_ascii(c: u8) -> u8 {
- if 0x40 <= c && c <= 0x5F {
- (c - 0x40) * 2 + 0x40
- } else if 0x60 <= c && c <= 0x7F {
- (c - 0x60) * 2 + 0x41
- } else {
- c
- }
-}
diff --git a/src/devices/file_device/bedrock_path_buffer.rs b/src/devices/file_device/bedrock_path_buffer.rs
deleted file mode 100644
index d6a0861..0000000
--- a/src/devices/file_device/bedrock_path_buffer.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-pub struct BedrockPathBuffer {
- buffer: [u8; 256],
- pointer: u8,
-}
-
-impl BedrockPathBuffer {
- pub fn new() -> Self {
- Self { buffer: [0; 256] , pointer: 0 }
- }
-
- /// Clear the buffer, returning the previous buffer contents.
- pub fn clear(&mut self) -> [u8; 256] {
- self.pointer = 0;
- std::mem::replace(&mut self.buffer, [0; 256])
- }
-
- /// Reset the pointer and hot-swap the byte buffer.
- pub fn populate(&mut self, buffer: [u8; 256]) {
- self.pointer = 0;
- self.buffer = buffer;
- }
-
- /// Move internal pointer to the start of the path or file name.
- ///
- /// If value is non-zero, the pointer will be moved to the byte
- /// directly following the final forward-slash.
- pub fn set_pointer(&mut self, value: u8) {
- self.pointer = 0;
- // Set the pointer to the start of the filename if value is truthy.
- if value != 0x00 {
- for (i, c) in self.buffer.iter().enumerate() {
- match c {
- b'/' => self.pointer = (i as u8).saturating_add(1),
- 0x00 => break,
- _ => continue,
- }
- }
- }
- }
-
- /// Read a single byte from the buffer.
- pub fn read(&mut self) -> u8 {
- let pointer = self.pointer as usize;
- self.pointer = self.pointer.wrapping_add(1);
- self.buffer[pointer]
- }
-
- /// Write a single byte to the buffer.
- ///
- /// If a null-byte is written, the buffer will be cleared and returned.
- pub fn write(&mut self, byte: u8) -> Option<[u8; 256]> {
- if byte == 0x00 {
- Some(self.clear())
- } else {
- self.buffer[self.pointer as usize] = byte;
- self.pointer = self.pointer.saturating_add(1);
- None
- }
- }
-}
diff --git a/src/devices/file_device/buffered_file.rs b/src/devices/file_device/buffered_file.rs
deleted file mode 100644
index 29e1fa3..0000000
--- a/src/devices/file_device/buffered_file.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use std::fs::File;
-use std::io::{BufReader, BufWriter};
-use std::io::{Read, Write};
-use std::io::{ErrorKind, Seek, SeekFrom};
-
-
-pub struct BufferedFile {
- file: AccessMode,
-}
-
-impl BufferedFile {
- pub fn new(file: File) -> Self {
- Self {
- file: AccessMode::Read(BufReader::new(file)),
- }
- }
-
- pub fn close(&mut self) {
- self.file = AccessMode::None;
- }
-
- pub fn read(&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,
- _ => { log::error!("BufferedFile::read: {error:?}"); 0 },
- }
- }
- }
-
- pub fn write(&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!(),
- };
- }
-
- pub fn flush(&mut self) {
- if let AccessMode::Write(writer) = &mut self.file {
- let _ = writer.flush();
- }
- }
-}
-
-impl Drop for BufferedFile {
- fn drop(&mut self) {
- self.flush()
- }
-}
-
-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
- }
-}
diff --git a/src/devices/file_device/directory_listing.rs b/src/devices/file_device/directory_listing.rs
deleted file mode 100644
index 465efc7..0000000
--- a/src/devices/file_device/directory_listing.rs
+++ /dev/null
@@ -1,120 +0,0 @@
-use super::*;
-
-
-pub struct DirectoryListing {
- children: Vec<BedrockFilePath>,
- length: u32,
- selected: Option<u32>,
- child_path_buffer: BedrockPathBuffer,
-}
-
-
-impl DirectoryListing {
- pub fn from_path(path: &BedrockFilePath) -> Option<Self> {
- macro_rules! unres {
- ($result:expr) => { match $result { Ok(v) => v, Err(_) => continue} };
- }
- macro_rules! unopt {
- ($option:expr) => { match $option { Some(v) => v, None => continue} };
- }
-
- #[cfg(target_family = "windows")] {
- if path.as_path().components().count() == 0 {
- return Some(Self::construct_virtual_root())
- }
- }
-
- let mut children = Vec::new();
- if let Ok(dir_listing) = std::fs::read_dir(path.as_path()) {
- for (i, entry_result) in dir_listing.enumerate() {
- // Firebreak to prevent emulator from consuming an absurd amount
- // of memory when opening too large of a directory.
- if i == (u16::MAX as usize) {
- break;
- }
-
- let entry = unres!(entry_result);
- let entry_path = unopt!(BedrockFilePath::from_path(&entry.path(), path.base()));
- if entry_path.is_hidden() {
- continue;
- }
-
- children.push(entry_path);
- }
- }
-
- children.sort();
- let length = u32::try_from(children.len()).ok()?;
- let selected = None;
- let child_path_buffer = BedrockPathBuffer::new();
- Some( Self { children, length, selected, child_path_buffer } )
- }
-
- /// Generate entries for a virtual root directory.
- #[cfg(target_family = "windows")]
- fn construct_virtual_root() -> Self {
- let mut children = Vec::new();
- let base = PathBuf::from("");
- let drive_bits = unsafe {
- windows::Win32::Storage::FileSystem::GetLogicalDrives()
- };
- for i in 0..26 {
- if drive_bits & (0x1 << i) != 0 {
- let letter: char = (b'A' + i).into();
- let path = PathBuf::from(format!("{letter}:/"));
- if let Some(drive) = BedrockFilePath::from_path(&path, &base) {
- children.push(drive);
- }
- }
- }
-
- let length = children.len() as u32;
- let selected = None;
- let child_path_buffer = BedrockPathBuffer::new();
- Self { children, length, selected, child_path_buffer }
- }
-
- /// Attempts to return a directory child by index.
- pub fn get(&self, index: u32) -> Option<&BedrockFilePath> {
- self.children.get(index as usize)
- }
-
- pub fn length(&self) -> u32 {
- self.length
- }
-
- /// Returns the index of the selected child, or zero if no child is selected.
- pub fn selected(&self) -> u32 {
- self.selected.unwrap_or(0)
- }
-
- /// Attempts to select a child by index.
- pub fn set_selected(&mut self, index: u32) {
- if let Some(child) = self.get(index) {
- let buffer = child.as_buffer();
- self.child_path_buffer.populate(buffer);
- self.selected = Some(index);
- } else {
- self.child_path_buffer.clear();
- self.selected = None;
- }
- }
-
- pub fn deselect_child(&mut self) {
- self.child_path_buffer.clear();
- self.selected = None;
- }
-
- pub fn child_path_buffer(&mut self) -> &mut BedrockPathBuffer {
- &mut self.child_path_buffer
- }
-
- pub fn child_type(&self) -> Option<EntryType> {
- self.selected.and_then(|s| self.get(s).and_then(|i| i.entry_type()))
- }
-
- pub fn child_path(&self) -> Option<BedrockFilePath> {
- self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.clone())))
- }
-}
-
diff --git a/src/devices/file_device/entry.rs b/src/devices/file_device/entry.rs
deleted file mode 100644
index d604bb7..0000000
--- a/src/devices/file_device/entry.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use super::*;
-
-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_device/operations.rs b/src/devices/file_device/operations.rs
deleted file mode 100644
index 3a3f81b..0000000
--- a/src/devices/file_device/operations.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-use std::io::ErrorKind;
-use std::path::Path;
-
-
-/// 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()
- }
-}
-
-/// 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()
-}
-
-/// Delete an entry, 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,
- }
- }
-}
-
-/// Returns true if an entry already exists at the given path.
-fn entry_exists(source: &Path) -> bool {
- std::fs::metadata(source).is_ok()
-}