Skip to content

Commit

Permalink
feat: windows mapped network devices (#189)
Browse files Browse the repository at this point in the history
* Cleanup gen_display_name

* wip

* Improve windows disks

* Fix rustfmt errors

* Fix clippy errors

* Fix dialog refresh with incorrect config

* Cleanup disks

* Fix rustfmt errors

* Update changelog

* Fix typo
  • Loading branch information
fluxxcode authored Nov 17, 2024
1 parent 2cd00cf commit 4008551
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added `FileDialog::update_with_right_panel_ui` to add a custom right panel to the file dialog [#170](https://github.com/fluxxcode/egui-file-dialog/pull/170) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!)
- Added `FileDialog::active_selected_entries` and `FileDialog::active_entry` to get information about the current active item/s [#170](https://github.com/fluxxcode/egui-file-dialog/pull/170) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!)
- Added option to show system files in the hamburger menu [#173](https://github.com/fluxxcode/egui-file-dialog/pull/173) (thanks [@crumblingstatue](https://github.com/crumblingstatue)!)
- Support mapped network devices on Windows [#189](https://github.com/fluxxcode/egui-file-dialog/pull/189)

### 🐛 Bug Fixes
- Fixed heading `Places` not being able to be updated with `FileDialogLabels` [#180](https://github.com/fluxxcode/egui-file-dialog/pull/180)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ serde = ["dep:serde"]
default_fonts = ["egui/default_fonts"]

[lints.rust]
unsafe_code = "forbid"
unsafe_code = "warn"

[lints.clippy]
nursery = { level = "deny", priority = 0 }
Expand Down
125 changes: 84 additions & 41 deletions src/data/disks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,32 @@ pub struct Disk {
}

impl Disk {
/// Create a new Disk object based on the data of a `sysinfo::Disk`.
pub fn from_sysinfo_disk(disk: &sysinfo::Disk, canonicalize_paths: bool) -> Self {
pub fn new(
name: Option<&str>,
mount_point: &Path,
is_removable: bool,
canonicalize_paths: bool,
) -> Self {
Self {
mount_point: Self::canonicalize(disk.mount_point(), canonicalize_paths),
display_name: gen_display_name(disk),
is_removable: disk.is_removable(),
mount_point: canonicalize(mount_point, canonicalize_paths),
display_name: gen_display_name(
name.unwrap_or_default(),
mount_point.to_str().unwrap_or_default(),
),
is_removable,
}
}

/// Create a new Disk object based on the data of a `sysinfo::Disk`.
pub fn from_sysinfo_disk(disk: &sysinfo::Disk, canonicalize_paths: bool) -> Self {
Self::new(
disk.name().to_str(),
disk.mount_point(),
disk.is_removable(),
canonicalize_paths,
)
}

/// Returns the mount point of the disk
pub fn mount_point(&self) -> &Path {
&self.mount_point
Expand All @@ -33,16 +50,6 @@ impl Disk {
pub const fn is_removable(&self) -> bool {
self.is_removable
}

/// Canonicalizes the given path.
/// Returns the input path in case of an error.
fn canonicalize(path: &Path, canonicalize: bool) -> PathBuf {
if canonicalize {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
} else {
path.to_path_buf()
}
}
}

/// Wrapper above the `sysinfo::Disks` struct
Expand All @@ -54,14 +61,9 @@ pub struct Disks {
impl Disks {
/// Creates a new Disks object with a refreshed list of the system disks.
pub fn new_with_refreshed_list(canonicalize_paths: bool) -> Self {
let disks = sysinfo::Disks::new_with_refreshed_list();

let mut result: Vec<Disk> = Vec::new();
for disk in &disks {
result.push(Disk::from_sysinfo_disk(disk, canonicalize_paths));
Self {
disks: load_disks(canonicalize_paths),
}

Self { disks: result }
}

/// Very simple wrapper method of the disks `.iter()` method.
Expand All @@ -71,39 +73,80 @@ impl Disks {
}
}

/// Canonicalizes the given path.
/// Returns the input path in case of an error.
fn canonicalize(path: &Path, canonicalize: bool) -> PathBuf {
if canonicalize {
dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
} else {
path.to_path_buf()
}
}

#[cfg(windows)]
fn gen_display_name(disk: &sysinfo::Disk) -> String {
// TODO: Get display name of the devices.
// Currently on Windows it returns an empty string for the C:\\ drive.

let mut name = disk.name().to_str().unwrap_or_default().to_string();
let mount_point = disk
.mount_point()
.to_str()
.unwrap_or_default()
.to_string()
.replace('\\', "");
fn gen_display_name(name: &str, mount_point: &str) -> String {
let mount_point = mount_point.replace('\\', "");

// Try using the mount point as the display name if the specified name
// from sysinfo::Disk is empty or contains invalid characters
if name.is_empty() {
return mount_point;
}

name.push_str(format!(" ({mount_point})").as_str());

name
format!("{name} ({mount_point})")
}

#[cfg(not(windows))]
fn gen_display_name(disk: &sysinfo::Disk) -> String {
let name = disk.name().to_str().unwrap_or_default().to_string();

fn gen_display_name(name: &str, mount_point: &str) -> String {
// Try using the mount point as the display name if the specified name
// from sysinfo::Disk is empty or contains invalid characters
if name.is_empty() {
return disk.mount_point().to_str().unwrap_or_default().to_string();
return mount_point.to_string();
}

name
name.to_string()
}

#[cfg(windows)]
fn load_disks(canonicalize_paths: bool) -> Vec<Disk> {
let mut disks: Vec<Disk> = sysinfo::Disks::new_with_refreshed_list()
.iter()
.map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths))
.collect();

// `sysinfo::Disks` currently do not include mapped network drives on Windows.
// We will load all other available drives using the Windows API.
// However, the sysinfo disks have priority, we are just adding to the list.
#[allow(unsafe_code)]
let mut drives = unsafe { GetLogicalDrives() };
let mut letter = b'A';

while drives > 0 {
if drives & 1 != 0 {
let path = PathBuf::from(format!("{}:\\", letter as char));
let mount_point = canonicalize(&path, canonicalize_paths);

if !disks.iter().any(|d| d.mount_point == mount_point) {
disks.push(Disk::new(None, &path, false, canonicalize_paths));
}
}

drives >>= 1;
letter += 1;
}

disks
}

#[cfg(windows)]
extern "C" {
pub fn GetLogicalDrives() -> u32;
}

#[cfg(not(windows))]
fn load_disks(canonicalize_paths: bool) -> Vec<Disk> {
sysinfo::Disks::new_with_refreshed_list()
.iter()
.map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths))
.collect()
}
9 changes: 8 additions & 1 deletion src/file_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ impl FileDialog {
pub fn with_config(config: FileDialogConfig) -> Self {
let mut obj = Self::new();
*obj.config_mut() = config;

obj.refresh();

obj
}

Expand Down Expand Up @@ -610,8 +613,12 @@ impl FileDialog {
/// you know what you are doing and have a reason for it.
/// Disabling canonicalization can lead to unexpected behavior, for example if an
/// already canonicalized path is then set as the initial directory.
pub const fn canonicalize_paths(mut self, canonicalize: bool) -> Self {
pub fn canonicalize_paths(mut self, canonicalize: bool) -> Self {
self.config.canonicalize_paths = canonicalize;

// Reload data like system disks and user directories with the updated canonicalization.
self.refresh();

self
}

Expand Down

0 comments on commit 4008551

Please sign in to comment.