egui_file_dialog/data/
disks.rs1#[cfg(target_os = "macos")]
2use std::fs;
3use std::path::{Path, PathBuf};
4
5#[derive(Default, Debug, Clone, PartialEq, Eq)]
9pub struct Disk {
10    mount_point: PathBuf,
11    display_name: String,
12    is_removable: bool,
13}
14
15impl Disk {
16    pub fn new(
18        name: Option<&str>,
19        mount_point: &Path,
20        is_removable: bool,
21        canonicalize_paths: bool,
22    ) -> Self {
23        Self {
24            mount_point: canonicalize(mount_point, canonicalize_paths),
25            display_name: gen_display_name(
26                name.unwrap_or_default(),
27                mount_point.to_str().unwrap_or_default(),
28            ),
29            is_removable,
30        }
31    }
32
33    pub fn from_sysinfo_disk(disk: &sysinfo::Disk, canonicalize_paths: bool) -> Self {
35        Self::new(
36            disk.name().to_str(),
37            disk.mount_point(),
38            disk.is_removable(),
39            canonicalize_paths,
40        )
41    }
42
43    #[cfg(target_os = "macos")]
45    pub fn from_path(path: &Path, canonicalize_paths: bool) -> Self {
46        let mount_point = canonicalize(path, canonicalize_paths);
47
48        let display_name = path.file_name().map_or_else(
50            || "Unknown".to_string(),
51            |name| name.to_string_lossy().to_string(),
52        );
53
54        let is_removable = false; Self {
59            mount_point,
60            display_name,
61            is_removable,
62        }
63    }
64
65    pub fn mount_point(&self) -> &Path {
67        &self.mount_point
68    }
69
70    pub fn display_name(&self) -> &str {
72        &self.display_name
73    }
74
75    pub const fn is_removable(&self) -> bool {
77        self.is_removable
78    }
79}
80
81#[derive(Default, Debug)]
83pub struct Disks {
84    disks: Vec<Disk>,
85}
86
87impl Disks {
88    pub const fn new(disks: Vec<Disk>) -> Self {
90        Self { disks }
91    }
92
93    pub fn new_native_disks(canonicalize_paths: bool) -> Self {
95        Self {
96            disks: load_disks(canonicalize_paths),
97        }
98    }
99
100    pub const fn new_empty() -> Self {
102        Self { disks: Vec::new() }
103    }
104
105    pub(crate) fn iter(&self) -> std::slice::Iter<'_, Disk> {
108        self.disks.iter()
109    }
110}
111
112impl<'a> IntoIterator for &'a Disks {
113    type IntoIter = std::slice::Iter<'a, Disk>;
114    type Item = &'a Disk;
115    fn into_iter(self) -> Self::IntoIter {
116        self.iter()
117    }
118}
119
120fn canonicalize(path: &Path, canonicalize: bool) -> PathBuf {
123    if canonicalize {
124        dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
125    } else {
126        path.to_path_buf()
127    }
128}
129
130#[cfg(windows)]
131fn gen_display_name(name: &str, mount_point: &str) -> String {
132    let mount_point = mount_point.replace('\\', "");
133
134    if name.is_empty() {
137        return mount_point;
138    }
139
140    format!("{name} ({mount_point})")
141}
142
143#[cfg(not(windows))]
144fn gen_display_name(name: &str, mount_point: &str) -> String {
145    if name.is_empty() {
148        return mount_point.to_string();
149    }
150
151    name.to_string()
152}
153
154#[cfg(windows)]
155fn load_disks(canonicalize_paths: bool) -> Vec<Disk> {
156    let mut disks: Vec<Disk> = sysinfo::Disks::new_with_refreshed_list()
157        .iter()
158        .map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths))
159        .collect();
160
161    #[allow(unsafe_code)]
165    let mut drives = unsafe { GetLogicalDrives() };
166    let mut letter = b'A';
167
168    while drives > 0 {
169        if drives & 1 != 0 {
170            let path = PathBuf::from(format!("{}:\\", letter as char));
171            let mount_point = canonicalize(&path, canonicalize_paths);
172
173            if !disks.iter().any(|d| d.mount_point == mount_point) {
174                disks.push(Disk::new(None, &path, false, canonicalize_paths));
175            }
176        }
177
178        drives >>= 1;
179        letter += 1;
180    }
181
182    disks
183}
184
185#[cfg(windows)]
186extern "C" {
187    pub fn GetLogicalDrives() -> u32;
188}
189
190#[cfg(all(not(windows), not(target_os = "macos")))]
191fn load_disks(canonicalize_paths: bool) -> Vec<Disk> {
192    sysinfo::Disks::new_with_refreshed_list()
193        .iter()
194        .map(|d| Disk::from_sysinfo_disk(d, canonicalize_paths))
195        .collect()
196}
197
198#[cfg(target_os = "macos")]
200fn load_disks(canonicalize_paths: bool) -> Vec<Disk> {
201    let mut result = Vec::new();
202    let mut seen_mount_points = std::collections::HashSet::new();
203
204    for disk in &sysinfo::Disks::new_with_refreshed_list() {
206        let mount_point = disk.mount_point();
207        if mount_point != Path::new("/")
208            && seen_mount_points.insert(mount_point.to_path_buf())
209            && disk.mount_point() != Path::new("/System/Volumes/Data")
210        {
211            result.push(Disk::from_sysinfo_disk(disk, canonicalize_paths));
212        }
213    }
214
215    if let Ok(entries) = fs::read_dir("/Volumes") {
217        for entry in entries.filter_map(Result::ok) {
218            let path = entry.path();
219            if seen_mount_points.insert(path.clone()) {
220                if let Some(name_osstr) = path.file_name() {
221                    if let Some(name) = name_osstr.to_str() {
222                        if path.is_dir() && !name.starts_with('.') {
223                            result.push(Disk::from_path(&path, canonicalize_paths));
224                        }
225                    }
226                }
227            }
228        }
229    }
230
231    result
232}