egui_file_dialog/
file_system.rs

1use std::io::{self, Read};
2use std::path::{Path, PathBuf};
3
4use crate::data::{Disks, Metadata, UserDirectories};
5
6/// An abstraction over the host system, allowing the file dialog to be used to browse e.g. in
7/// memory filesystems.
8///
9/// # Examples
10///
11/// ```
12/// use egui_file_dialog::{FileDialog, FileSystem, UserDirectories, Metadata, Disks};
13/// use std::{io, path::{Path, PathBuf}};
14///
15/// struct MyFileSystem;
16///
17/// impl FileSystem for MyFileSystem {
18///     fn metadata(&self, path: &Path) -> io::Result<Metadata> { todo!() }
19///     fn is_dir(&self, path: &Path) -> bool { todo!() }
20///     fn is_file(&self, path: &Path) -> bool { todo!() }
21///     fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> { todo!() }
22///     fn get_disks(&self, canonicalize_paths: bool) -> Disks { todo!() }
23///     fn is_path_hidden(&self, path: &Path) -> bool { todo!() }
24///     fn create_dir(&self, path: &Path) -> io::Result<()> { todo!() }
25///     fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories> { todo!() }
26///     fn current_dir(&self) -> io::Result<PathBuf> { Ok("/".into()) }
27/// }
28///
29/// let dialog = FileDialog::with_file_system(std::sync::Arc::new(MyFileSystem));
30///
31/// /* Use the file dialog as usual */
32/// ```
33pub trait FileSystem {
34    /// Queries metadata for the given path
35    fn metadata(&self, path: &Path) -> io::Result<Metadata>;
36
37    /// Returns true if the path exists and is a directory
38    fn is_dir(&self, path: &Path) -> bool;
39
40    /// Returns true if the path exists and is a file
41    fn is_file(&self, path: &Path) -> bool;
42
43    /// Gets the children of a directory
44    fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>>;
45
46    /// List out the disks in the system
47    fn get_disks(&self, canonicalize_paths: bool) -> Disks;
48
49    /// Determine if a path is hidden
50    fn is_path_hidden(&self, path: &Path) -> bool;
51
52    /// Creates a new directory
53    fn create_dir(&self, path: &Path) -> io::Result<()>;
54
55    /// Returns the user directories
56    fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories>;
57
58    /// Get the current working directory
59    fn current_dir(&self) -> io::Result<PathBuf>;
60
61    /// Read a short preview of a text file
62    fn load_text_file_preview(&self, _path: &Path, _max_chars: usize) -> io::Result<String> {
63        Err(std::io::Error::new(
64            std::io::ErrorKind::Unsupported,
65            "load_text_file_preview not implemented.".to_string(),
66        ))
67    }
68}
69
70impl std::fmt::Debug for dyn FileSystem + Send + Sync {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "<FileSystem>")
73    }
74}
75
76/// Implementation of `FileSystem` using the standard library
77pub struct NativeFileSystem;
78
79impl FileSystem for NativeFileSystem {
80    fn metadata(&self, path: &Path) -> io::Result<Metadata> {
81        let mut metadata = Metadata::default();
82
83        let md = std::fs::metadata(path)?;
84        metadata.size = Some(md.len());
85        metadata.last_modified = md.modified().ok();
86        metadata.created = md.created().ok();
87        metadata.file_type = Some(format!("{:?}", md.file_type()));
88
89        Ok(metadata)
90    }
91
92    fn is_dir(&self, path: &Path) -> bool {
93        path.is_dir()
94    }
95
96    fn is_file(&self, path: &Path) -> bool {
97        path.is_file()
98    }
99
100    fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
101        Ok(std::fs::read_dir(path)?
102            .filter_map(Result::ok)
103            .map(|entry| entry.path())
104            .collect())
105    }
106
107    fn load_text_file_preview(&self, path: &Path, max_chars: usize) -> io::Result<String> {
108        let mut file = std::fs::File::open(path)?;
109        let mut chunk = [0; 96]; // Temporary buffer
110        let mut buffer = String::new();
111
112        // Add the first chunk to the buffer as text
113        let mut total_read = 0;
114
115        // Continue reading if needed
116        while total_read < max_chars {
117            let bytes_read = file.read(&mut chunk)?;
118            if bytes_read == 0 {
119                break; // End of file
120            }
121            let chars_read: String = String::from_utf8(chunk[..bytes_read].to_vec())
122                .map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
123            total_read += chars_read.len();
124            buffer.push_str(&chars_read);
125        }
126
127        Ok(buffer.to_string())
128    }
129
130    fn get_disks(&self, canonicalize_paths: bool) -> Disks {
131        Disks::new_native_disks(canonicalize_paths)
132    }
133
134    fn is_path_hidden(&self, path: &Path) -> bool {
135        is_path_hidden(path)
136    }
137
138    fn create_dir(&self, path: &Path) -> io::Result<()> {
139        std::fs::create_dir(path)
140    }
141
142    fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories> {
143        if let Some(dirs) = directories::UserDirs::new() {
144            return Some(UserDirectories::new(
145                UserDirectories::canonicalize(Some(dirs.home_dir()), canonicalize_paths),
146                UserDirectories::canonicalize(dirs.audio_dir(), canonicalize_paths),
147                UserDirectories::canonicalize(dirs.desktop_dir(), canonicalize_paths),
148                UserDirectories::canonicalize(dirs.document_dir(), canonicalize_paths),
149                UserDirectories::canonicalize(dirs.download_dir(), canonicalize_paths),
150                UserDirectories::canonicalize(dirs.picture_dir(), canonicalize_paths),
151                UserDirectories::canonicalize(dirs.video_dir(), canonicalize_paths),
152            ));
153        }
154
155        None
156    }
157
158    fn current_dir(&self) -> io::Result<PathBuf> {
159        std::env::current_dir()
160    }
161}
162
163#[cfg(windows)]
164fn is_path_hidden(path: &Path) -> bool {
165    use std::os::windows::fs::MetadataExt;
166
167    std::fs::metadata(path).is_ok_and(|metadata| metadata.file_attributes() & 0x2 > 0)
168}
169
170#[cfg(not(windows))]
171fn is_path_hidden(path: &Path) -> bool {
172    let Some(file_name) = path.file_name() else {
173        return false;
174    };
175    let Some(s) = file_name.to_str() else {
176        return false;
177    };
178
179    if s.starts_with('.') {
180        return true;
181    }
182
183    false
184}