diff options
Diffstat (limited to 'src/devices/file_device')
-rw-r--r-- | src/devices/file_device/bedrock_file_path.rs | 287 | ||||
-rw-r--r-- | src/devices/file_device/bedrock_path_buffer.rs | 60 | ||||
-rw-r--r-- | src/devices/file_device/buffered_file.rs | 144 | ||||
-rw-r--r-- | src/devices/file_device/directory_listing.rs | 120 | ||||
-rw-r--r-- | src/devices/file_device/entry.rs | 36 | ||||
-rw-r--r-- | src/devices/file_device/operations.rs | 47 |
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() -} |