summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2024-08-07 17:09:14 +1200
committerBen Bridle <ben@derelict.engineering>2024-08-07 17:09:14 +1200
commit38d40a2c5d4b553f524d87755b8e2e0e47928b8a (patch)
tree01fd01820be4219ca9f3dc7ad6e61eb183ade963
parent65b53003e8de9543ba25a3b3d3cace399b92dc1d (diff)
downloadbedrock-pc-38d40a2c5d4b553f524d87755b8e2e0e47928b8a.zip
Refactor the file device
This is the Windows side of the refactoring job. The windows crate has been added as a dependency in order to get a list of available drives by drive letter, and a virtual top-level root directory has been implemented in the Windows code to make it possible for programs to hierarchically navigate between available drives.
-rw-r--r--Cargo.lock120
-rw-r--r--Cargo.toml6
-rw-r--r--src/devices/file.rs52
-rw-r--r--src/devices/file/bedrock_file_path.rs165
-rw-r--r--src/devices/file/directory_child.rs35
-rw-r--r--src/devices/file/directory_listing.rs76
6 files changed, 296 insertions, 158 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1eaaf78..2a64d8b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -52,6 +52,7 @@ dependencies = [
"mini_paste",
"phosphor",
"time",
+ "windows",
]
[[package]]
@@ -1323,6 +1324,70 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
+name = "windows"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
+dependencies = [
+ "windows-core",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.42",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1346,7 +1411,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets 0.52.0",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -1381,17 +1446,18 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm 0.52.0",
- "windows_aarch64_msvc 0.52.0",
- "windows_i686_gnu 0.52.0",
- "windows_i686_msvc 0.52.0",
- "windows_x86_64_gnu 0.52.0",
- "windows_x86_64_gnullvm 0.52.0",
- "windows_x86_64_msvc 0.52.0",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
]
[[package]]
@@ -1408,9 +1474,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
@@ -1426,9 +1492,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
@@ -1444,9 +1510,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.0"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
@@ -1462,9 +1534,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
@@ -1480,9 +1552,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -1498,9 +1570,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
@@ -1516,9 +1588,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.0"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winit"
diff --git a/Cargo.toml b/Cargo.toml
index b1408ce..7b07fdb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,6 @@ authors = ["Ben Bridle"]
edition = "2021"
description = "Emulator for running Bedrock programs"
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bedrock_core = { git = "git://benbridle.com/bedrock_core", tag = "v3.0.1" }
@@ -15,6 +14,11 @@ geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" }
mini_paste = "0.1.11"
time = { version = "0.3.30", features = [ "local-offset" ] }
+
+[target.'cfg(target_os = "windows")'.dependencies]
+windows = { version = "0.58.0", features = ["Win32_Storage_FileSystem"] }
+
+
[profile.release]
lto=true
opt-level="s"
diff --git a/src/devices/file.rs b/src/devices/file.rs
index 26e14da..bf185d7 100644
--- a/src/devices/file.rs
+++ b/src/devices/file.rs
@@ -1,7 +1,6 @@
mod bedrock_file_path;
mod buffered_file;
mod circular_path_buffer;
-mod directory_child;
mod directory_listing;
mod entry;
mod operations;
@@ -9,7 +8,6 @@ mod operations;
pub use bedrock_file_path::*;
pub use buffered_file::*;
pub use circular_path_buffer::*;
-pub use directory_child::*;
pub use directory_listing::*;
pub use entry::*;
use operations::*;
@@ -100,32 +98,32 @@ impl FileDevice {
/// Opens the entry at the given path.
pub fn open_entry(&mut self, path: BedrockFilePath) -> Result<(), ()> {
- macro_rules! unres {
- ($result:expr) => { match $result {Ok(v)=>v,Err(_)=>return Err(())} };
+ 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) => {
+ // 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 => (),
}
- let absolute_path = path.as_path();
- let metadata = unres!(std::fs::metadata(&absolute_path));
- if metadata.is_file() {
- let open_result = std::fs::OpenOptions::new()
- .read(self.enable_read)
- .write(self.enable_write)
- .open(&absolute_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(());
- };
- } else if metadata.is_dir() {
- // Keep the current entry open if we can't open the new path.
- if let Some(listing) = path.directory_listing() {
- self.close_entry();
- self.name_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::Directory(listing), path));
- return Ok(());
- };
- };
return Err(());
}
diff --git a/src/devices/file/bedrock_file_path.rs b/src/devices/file/bedrock_file_path.rs
index c169a62..e083853 100644
--- a/src/devices/file/bedrock_file_path.rs
+++ b/src/devices/file/bedrock_file_path.rs
@@ -3,17 +3,13 @@ use super::*;
use std::cmp::Ordering;
use std::ffi::OsString;
-#[cfg(target_family = "unix")]
-use std::os::unix::ffi::OsStringExt;
-#[cfg(target_family = "windows")]
-use std::os::windows::ffi::OsStringExt;
-
#[derive(Clone)]
pub struct BedrockFilePath {
base: PathBuf,
relative: PathBuf,
bytes: Vec<u8>,
+ entry_type: Option<EntryType>,
}
impl BedrockFilePath {
@@ -21,8 +17,9 @@ impl BedrockFilePath {
let base = base.to_path_buf();
let relative = buffer_to_path(buffer)?;
let bytes = path_to_bytes(&relative)?;
- assert_path_is_safe(&relative)?;
- Some(Self { base, relative, bytes })
+ 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.
@@ -30,8 +27,9 @@ impl BedrockFilePath {
let base = base.to_path_buf();
let relative = path.strip_prefix(&base).ok()?.to_path_buf();
let bytes = path_to_bytes(&relative)?;
- assert_path_is_safe(&relative)?;
- Some( Self { base, relative, bytes } )
+ 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.
@@ -59,32 +57,60 @@ impl BedrockFilePath {
self.base.join(&self.relative)
}
- /// Get a path which represents the parent of this path.
- pub fn parent(&self) -> Option<Self> {
- let relative = self.relative.parent()?.to_path_buf();
- let base = self.base.clone();
- let bytes = path_to_bytes(&relative)?;
- Some( Self { base, relative, bytes } )
+ /// Get the entry type of this path.
+ pub fn entry_type(&self) -> Option<EntryType> {
+ self.entry_type
}
- pub fn directory_listing(&self) -> Option<DirectoryListing> {
- DirectoryListing::from_path(&self)
+ /// Get a path which represents the parent of this path.
+ pub fn parent(&self) -> Option<Self> {
+ #[cfg(target_family = "unix")] {
+ Self::from_path(self.relative.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.relative.parent()?, &self.base)
+ } else {
+ // Unsandboxed path, we can ascend to a virtual root directory.
+ match self.relative.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 a dot character directly follows the final
- /// forward-slash character in the relative path.
- pub fn filename_is_dot_prefixed(&self) -> bool {
- let bytes = self.as_bytes();
- let mut dot_i = 0;
- for (i, b) in bytes.iter().enumerate() {
- if *b == b'/' {
- // Guaranteed to be a valid index, bytes is null-terminated.
- dot_i = i + 1;
- } else if *b == 0x00 {
- break;
+ /// 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 bytes[dot_i] == b'.';
+ return false;
}
}
@@ -108,11 +134,19 @@ fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> {
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")] {
- let string = String::from_utf8_lossy(slice);
+ 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())
}
@@ -122,14 +156,15 @@ fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> {
///
/// A byte path contains at most 255 bytes, and is not null-terminated.
fn path_to_bytes(path: &Path) -> Option<Vec<u8>> {
- let mut string = String::from("/");
#[cfg(target_family = "unix")]
- string.push_str(path.as_os_str().to_str()?);
+ let string = path.as_os_str().to_str()?.to_string();
#[cfg(target_family = "windows")]
- string.push_str(&path.as_os_str().to_str()?.replace(r"\", r"/"));
+ let string = path.as_os_str().to_str()?.replace(r"\", r"/");
- // Remove all trailing forward-slash characters.
- let slice = string.trim_end_matches('/').as_bytes();
+ // 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; }
@@ -137,24 +172,63 @@ fn path_to_bytes(path: &Path) -> Option<Vec<u8>> {
Some(Vec::from(slice))
}
-/// Returns true if a path contains only normal components.
-fn assert_path_is_safe(relative: &Path) -> Option<()> {
- // Error if path contains special components.
- for component in relative.components() {
- match component {
- Component::Normal(_) => continue,
- _ => return None,
+/// 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.bytes == other.bytes && self.entry_type == other.entry_type
}
}
@@ -168,7 +242,10 @@ impl PartialOrd for BedrockFilePath {
impl Ord for BedrockFilePath {
fn cmp(&self, other: &Self) -> Ordering {
- compare_ascii_slices(&self.bytes, &other.bytes)
+ match self.entry_type.cmp(&other.entry_type) {
+ Ordering::Equal => compare_ascii_slices(&self.bytes, &other.bytes),
+ ordering => ordering,
+ }
}
}
diff --git a/src/devices/file/directory_child.rs b/src/devices/file/directory_child.rs
deleted file mode 100644
index 376ec7d..0000000
--- a/src/devices/file/directory_child.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-use super::*;
-
-use std::cmp::Ordering;
-
-
-pub struct DirectoryChild {
- pub path: BedrockFilePath,
- pub entry_type: EntryType,
-}
-
-
-// ---------------------------------------------------------------------------
-
-impl PartialEq for DirectoryChild {
- fn eq(&self, other: &Self) -> bool {
- self.entry_type == other.entry_type && self.path == other.path
- }
-}
-
-impl Eq for DirectoryChild {}
-
-impl PartialOrd for DirectoryChild {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- Some(self.cmp(other))
- }
-}
-
-impl Ord for DirectoryChild {
- fn cmp(&self, other: &Self) -> Ordering {
- match self.entry_type.cmp(&other.entry_type) {
- Ordering::Equal => self.path.cmp(&other.path),
- ordering => ordering,
- }
- }
-}
diff --git a/src/devices/file/directory_listing.rs b/src/devices/file/directory_listing.rs
index 0cbbde9..febc5c2 100644
--- a/src/devices/file/directory_listing.rs
+++ b/src/devices/file/directory_listing.rs
@@ -2,12 +2,13 @@ use super::*;
pub struct DirectoryListing {
- children: Vec<DirectoryChild>,
+ children: Vec<BedrockFilePath>,
length: u32,
selected: Option<u32>,
name_buffer: CircularPathBuffer,
}
+
impl DirectoryListing {
pub fn from_path(path: &BedrockFilePath) -> Option<Self> {
macro_rules! unres {
@@ -17,32 +18,29 @@ impl DirectoryListing {
($option:expr) => { match $option { Some(v) => v, None => continue} };
}
- let mut children = Vec::new();
- let dir_listing = std::fs::read_dir(path.as_path()).ok()?;
- 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;
+ #[cfg(target_family = "windows")] {
+ if path.as_path().components().count() == 0 {
+ return Some(Self::construct_virtual_root())
}
+ }
- let entry = unres!(entry_result);
- let entry_path = unopt!(BedrockFilePath::from_path(&entry.path(), path.base()));
- if entry_path.filename_is_dot_prefixed() {
- continue;
- }
- let metadata = unres!(std::fs::metadata(&entry.path()));
- let entry_type =
- if metadata.is_file() {
- EntryType::File
- } else if metadata.is_dir() {
- EntryType::Directory
- } else {
+ 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;
- };
+ }
- let child = DirectoryChild { path: entry_path, entry_type };
- children.push(child);
+ children.push(entry_path);
+ }
}
children.sort();
@@ -52,8 +50,32 @@ impl DirectoryListing {
Some( Self { children, length, selected, name_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 name_buffer = CircularPathBuffer::new();
+ Self { children, length, selected, name_buffer }
+ }
+
/// Attempts to return a directory child by index.
- pub fn get(&self, index: u32) -> Option<&DirectoryChild> {
+ pub fn get(&self, index: u32) -> Option<&BedrockFilePath> {
self.children.get(index as usize)
}
@@ -69,7 +91,7 @@ impl DirectoryListing {
/// 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.path.as_buffer();
+ let buffer = child.as_buffer();
self.name_buffer.populate(buffer);
self.selected = Some(index);
} else {
@@ -83,11 +105,11 @@ impl DirectoryListing {
}
pub fn child_type(&self) -> Option<EntryType> {
- self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.entry_type)))
+ 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.path.clone())))
+ self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.clone())))
}
}