Skip to main content

bladvak/
file_handler.rs

1//! File handler
2
3use eframe::egui;
4use poll_promise::Promise;
5use std::{fmt::Debug, fs::read, path::PathBuf};
6
7use crate::errors::AppError;
8
9/// File object
10#[derive(Default, Clone)]
11pub struct File {
12    /// File data
13    pub data: Vec<u8>,
14    /// Path or filename
15    pub path: PathBuf,
16}
17
18/// File Handler
19#[derive(Default, serde::Deserialize, serde::Serialize)]
20pub struct FileHandler {
21    /// Dropped files handler
22    #[serde(skip)]
23    pub dropped_files: Vec<egui::DroppedFile>,
24
25    /// File upload handling
26    #[serde(skip)]
27    pub file_upload: Option<Promise<Result<FileState, AppError>>>,
28}
29
30/// File state
31#[derive(Clone)]
32pub enum FileState {
33    /// File is not selected
34    NotSelected,
35    /// File is being uploaded or selected
36    UploadedOrSelected,
37    /// No file upload
38    NoUpload,
39    /// File is ready
40    Ready(File),
41}
42
43impl Debug for FileHandler {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        let mut debug_fmt = f.debug_struct("FileHandler");
46        debug_fmt.field("dropped_files", &self.dropped_files);
47        if self.file_upload.is_some() {
48            let val = String::new();
49            debug_fmt.field("file_upload", &val);
50        }
51        debug_fmt.finish()
52    }
53}
54
55impl FileHandler {
56    /// Handle the file
57    #[cfg(target_arch = "wasm32")]
58    pub fn handle_file_open(&mut self) {
59        self.file_upload = Some(Promise::spawn_local(async {
60            log::info!("rfd start");
61            let file_selected = rfd::AsyncFileDialog::new().pick_file().await;
62            log::info!("rfd result {:?}", file_selected);
63            if let Some(curr_file) = file_selected {
64                let buf = curr_file.read().await;
65                return Ok(FileState::Ready(File {
66                    data: buf,
67                    path: PathBuf::from(curr_file.file_name()),
68                }));
69            }
70            // no file selected
71            Ok(FileState::NotSelected)
72        }));
73    }
74
75    /// Handle the file
76    #[cfg(not(target_arch = "wasm32"))]
77    pub fn handle_file_open(&mut self) {
78        self.file_upload = Some(Promise::spawn_thread("slow", move || {
79            if let Some(path_buf) = rfd::FileDialog::new().pick_file() {
80                // read file as string
81                if let Some(path) = path_buf.to_str() {
82                    let buf = std::fs::read(path);
83                    let buf = match buf {
84                        Ok(v) => v,
85                        Err(e) => {
86                            log::warn!("{e:?}");
87                            return Err(AppError::new(e.to_string()));
88                        }
89                    };
90                    return Ok(FileState::Ready(File {
91                        data: buf,
92                        path: path_buf,
93                    }));
94                }
95                return Err(AppError::new("Invalid file path".to_string()));
96            }
97            // no file selected
98            Ok(FileState::NotSelected)
99        }));
100    }
101
102    /// Reset the `file_handler`
103    pub fn reset(&mut self) {
104        self.file_upload = None;
105    }
106
107    /// Handle file dropped
108    fn handle_file_dropped(&mut self) -> Result<Option<File>, AppError> {
109        if self.dropped_files.is_empty() {
110            return Ok(None);
111        }
112        let file = self.dropped_files.remove(0);
113        if cfg!(not(target_arch = "wasm32")) {
114            if let Some(path) = file.path.as_deref() {
115                let file = read(path)?;
116                return Ok(Some(File {
117                    data: file,
118                    path: path.to_path_buf(),
119                }));
120            }
121        } else if cfg!(target_arch = "wasm32")
122            && let Some(bytes) = file.bytes.as_deref()
123        {
124            return Ok(Some(File {
125                data: bytes.to_vec(),
126                path: file.path.unwrap_or(PathBuf::from(file.name)),
127            }));
128        }
129        Ok(None)
130    }
131
132    /// Handle the files
133    /// # Errors
134    /// Can return an error if fails to handle files
135    pub fn handle_files(&mut self, ctx: &egui::Context) -> Result<Option<File>, AppError> {
136        ctx.input(|i| {
137            if !i.raw.dropped_files.is_empty() {
138                // read the first file
139                self.dropped_files.clone_from(&i.raw.dropped_files);
140            }
141        });
142        let file_upload_state = match &self.file_upload {
143            Some(result) => match result.ready() {
144                Some(Ok(state)) => Ok(state.clone()),
145                Some(Err(e)) => Err(e.clone()),
146                None => Ok(FileState::UploadedOrSelected), // promise not ready
147            },
148            None => Ok(FileState::NoUpload), // no file upload
149        };
150        match file_upload_state {
151            Ok(state) => match state {
152                FileState::NotSelected => {
153                    log::info!("No file selected");
154                    self.reset();
155                }
156                FileState::UploadedOrSelected => {
157                    log::info!("File is being uploaded or selected...");
158                    ctx.request_repaint();
159                    return Ok(None);
160                }
161                FileState::Ready(data) => {
162                    log::info!("File uploaded successfully");
163                    self.reset();
164                    return Ok(Some(data));
165                }
166                FileState::NoUpload => {
167                    self.reset();
168                }
169            },
170            Err(e) => {
171                self.reset();
172                return Err(e);
173            }
174        }
175        if let Some(file_dropped) = self.handle_file_dropped()? {
176            return Ok(Some(file_dropped));
177        }
178        Ok(None)
179    }
180}