egui_file_dialog/
file_system.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use std::io::{self, Read};
use std::path::{Path, PathBuf};

use crate::data::{Disks, Metadata, UserDirectories};

/// An abstraction over the host system, allowing the file dialog to be used to browse e.g. in
/// memory filesystems.
///
/// # Examples
///
/// ```
/// use egui_file_dialog::{FileDialog, FileSystem, UserDirectories, Metadata, Disks};
/// use std::{io, path::{Path, PathBuf}};
///
/// struct MyFileSystem;
///
/// impl FileSystem for MyFileSystem {
///     fn metadata(&self, path: &Path) -> io::Result<Metadata> { todo!() }
///     fn is_dir(&self, path: &Path) -> bool { todo!() }
///     fn is_file(&self, path: &Path) -> bool { todo!() }
///     fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> { todo!() }
///     fn get_disks(&self, canonicalize_paths: bool) -> Disks { todo!() }
///     fn is_path_hidden(&self, path: &Path) -> bool { todo!() }
///     fn create_dir(&self, path: &Path) -> io::Result<()> { todo!() }
///     fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories> { todo!() }
///     fn current_dir(&self) -> io::Result<PathBuf> { Ok("/".into()) }
/// }
///
/// let dialog = FileDialog::with_file_system(std::sync::Arc::new(MyFileSystem));
///
/// /* Use the file dialog as usual */
/// ```
pub trait FileSystem {
    /// Queries metadata for the given path
    fn metadata(&self, path: &Path) -> io::Result<Metadata>;

    /// Returns true if the path exists and is a directory
    fn is_dir(&self, path: &Path) -> bool;

    /// Returns true if the path exists and is a file
    fn is_file(&self, path: &Path) -> bool;

    /// Gets the children of a directory
    fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>>;

    /// List out the disks in the system
    fn get_disks(&self, canonicalize_paths: bool) -> Disks;

    /// Determine if a path is hidden
    fn is_path_hidden(&self, path: &Path) -> bool;

    /// Creates a new directory
    fn create_dir(&self, path: &Path) -> io::Result<()>;

    /// Returns the user directories
    fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories>;

    /// Get the current working directory
    fn current_dir(&self) -> io::Result<PathBuf>;

    /// Read a short preview of a text file
    fn load_text_file_preview(&self, _path: &Path, _max_chars: usize) -> io::Result<String> {
        Err(std::io::Error::new(
            std::io::ErrorKind::Unsupported,
            "load_text_file_preview not implemented.".to_string(),
        ))
    }
}

impl std::fmt::Debug for dyn FileSystem + Send + Sync {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "<FileSystem>")
    }
}

/// Implementation of `FileSystem` using the standard library
pub struct NativeFileSystem;

impl FileSystem for NativeFileSystem {
    fn metadata(&self, path: &Path) -> io::Result<Metadata> {
        let mut metadata = Metadata::default();

        let md = std::fs::metadata(path)?;
        metadata.size = Some(md.len());
        metadata.last_modified = md.modified().ok();
        metadata.created = md.created().ok();
        metadata.file_type = Some(format!("{:?}", md.file_type()));

        Ok(metadata)
    }

    fn is_dir(&self, path: &Path) -> bool {
        path.is_dir()
    }

    fn is_file(&self, path: &Path) -> bool {
        path.is_file()
    }

    fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
        Ok(std::fs::read_dir(path)?
            .filter_map(Result::ok)
            .map(|entry| entry.path())
            .collect())
    }

    fn load_text_file_preview(&self, path: &Path, max_chars: usize) -> io::Result<String> {
        let mut file = std::fs::File::open(path)?;
        let mut chunk = [0; 96]; // Temporary buffer
        let mut buffer = String::new();

        // Add the first chunk to the buffer as text
        let mut total_read = 0;

        // Continue reading if needed
        while total_read < max_chars {
            let bytes_read = file.read(&mut chunk)?;
            if bytes_read == 0 {
                break; // End of file
            }
            let chars_read: String = String::from_utf8(chunk[..bytes_read].to_vec())
                .map_err(|_| io::Error::from(io::ErrorKind::InvalidData))?;
            total_read += chars_read.len();
            buffer.push_str(&chars_read);
        }

        Ok(buffer.to_string())
    }

    fn get_disks(&self, canonicalize_paths: bool) -> Disks {
        Disks::new_native_disks(canonicalize_paths)
    }

    fn is_path_hidden(&self, path: &Path) -> bool {
        is_path_hidden(path)
    }

    fn create_dir(&self, path: &Path) -> io::Result<()> {
        std::fs::create_dir(path)
    }

    fn user_dirs(&self, canonicalize_paths: bool) -> Option<UserDirectories> {
        if let Some(dirs) = directories::UserDirs::new() {
            return Some(UserDirectories::new(
                UserDirectories::canonicalize(Some(dirs.home_dir()), canonicalize_paths),
                UserDirectories::canonicalize(dirs.audio_dir(), canonicalize_paths),
                UserDirectories::canonicalize(dirs.desktop_dir(), canonicalize_paths),
                UserDirectories::canonicalize(dirs.document_dir(), canonicalize_paths),
                UserDirectories::canonicalize(dirs.download_dir(), canonicalize_paths),
                UserDirectories::canonicalize(dirs.picture_dir(), canonicalize_paths),
                UserDirectories::canonicalize(dirs.video_dir(), canonicalize_paths),
            ));
        }

        None
    }

    fn current_dir(&self) -> io::Result<PathBuf> {
        std::env::current_dir()
    }
}

#[cfg(windows)]
fn is_path_hidden(path: &Path) -> bool {
    use std::os::windows::fs::MetadataExt;

    std::fs::metadata(path).is_ok_and(|metadata| metadata.file_attributes() & 0x2 > 0)
}

#[cfg(not(windows))]
fn is_path_hidden(path: &Path) -> bool {
    let Some(file_name) = path.file_name() else {
        return false;
    };
    let Some(s) = file_name.to_str() else {
        return false;
    };

    if s.starts_with('.') {
        return true;
    }

    false
}