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 upload
108    fn handle_file_upload(&mut self) -> Result<FileState, AppError> {
109        match &self.file_upload {
110            Some(result) => match result.ready() {
111                Some(Ok(state)) => Ok(state.clone()),
112                Some(Err(e)) => Err(e.clone()),
113                None => Ok(FileState::UploadedOrSelected), // promise not ready
114            },
115            None => Ok(FileState::NoUpload), // no file upload
116        }
117    }
118
119    /// Handle file dropped
120    fn handle_file_dropped(&mut self) -> Result<Option<File>, AppError> {
121        if self.dropped_files.is_empty() {
122            return Ok(None);
123        }
124        let file = self.dropped_files.remove(0);
125        if cfg!(not(target_arch = "wasm32")) {
126            if let Some(path) = file.path.as_deref() {
127                let file = read(path)?;
128                return Ok(Some(File {
129                    data: file,
130                    path: path.to_path_buf(),
131                }));
132            }
133        } else if cfg!(target_arch = "wasm32")
134            && let Some(bytes) = file.bytes.as_deref()
135        {
136            return Ok(Some(File {
137                data: bytes.to_vec(),
138                path: file.path.unwrap_or(PathBuf::from(file.name)),
139            }));
140        }
141        Ok(None)
142    }
143
144    /// Handle the files
145    /// # Errors
146    /// Can return an error if fails to handle files
147    pub fn handle_files(&mut self, ctx: &egui::Context) -> Result<Option<File>, AppError> {
148        ctx.input(|i| {
149            if !i.raw.dropped_files.is_empty() {
150                // read the first file
151                self.dropped_files.clone_from(&i.raw.dropped_files);
152            }
153        });
154        match self.handle_file_upload() {
155            Ok(state) => match state {
156                FileState::NotSelected => {
157                    log::info!("No file selected");
158                    self.reset();
159                }
160                FileState::UploadedOrSelected => {
161                    log::info!("File is being uploaded or selected...");
162                    return Ok(None);
163                }
164                FileState::Ready(data) => {
165                    log::info!("File uploaded successfully");
166                    self.reset();
167                    return Ok(Some(data));
168                }
169                FileState::NoUpload => {
170                    self.reset();
171                }
172            },
173            Err(e) => {
174                self.reset();
175                return Err(e);
176            }
177        }
178        if let Some(file_dropped) = self.handle_file_dropped()? {
179            return Ok(Some(file_dropped));
180        }
181        Ok(None)
182    }
183}