diff options
author | Ben Bridle <ben@derelict.engineering> | 2024-08-07 17:09:14 +1200 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2024-08-07 17:09:14 +1200 |
commit | 38d40a2c5d4b553f524d87755b8e2e0e47928b8a (patch) | |
tree | 01fd01820be4219ca9f3dc7ad6e61eb183ade963 | |
parent | 65b53003e8de9543ba25a3b3d3cace399b92dc1d (diff) | |
download | bedrock-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.lock | 120 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | src/devices/file.rs | 52 | ||||
-rw-r--r-- | src/devices/file/bedrock_file_path.rs | 165 | ||||
-rw-r--r-- | src/devices/file/directory_child.rs | 35 | ||||
-rw-r--r-- | src/devices/file/directory_listing.rs | 76 |
6 files changed, 296 insertions, 158 deletions
@@ -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" @@ -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()))) } } |