hex_patch/app/files/
files.rs

1#![allow(clippy::module_inception)]
2use std::error::Error;
3
4use ratatui::{backend::Backend, Terminal};
5
6use crate::{
7    app::{
8        data::Data, info_mode::InfoMode, log::NotificationLevel, popup::popup_state::PopupState,
9        App,
10    },
11    get_app_context,
12    headers::Header,
13};
14
15use super::{filesystem::FileSystem, path, path_result::PathResult};
16
17impl App {
18    pub(in crate::app) fn go_to_path<B: Backend>(
19        &mut self,
20        currently_open_path: &str,
21        path: &str,
22        scroll: usize,
23        popup: &mut Option<PopupState>,
24        terminal: &mut Terminal<B>,
25    ) -> Result<(), Box<dyn Error>> {
26        let contents = Self::find_dir_contents(currently_open_path, path, &self.filesystem)?;
27        if contents.is_empty() {
28            return Err(t!("app.messages.no_file_match", path = path).into());
29        }
30        let selected = contents
31            .into_iter()
32            .nth(scroll)
33            .expect(&t!("errors.go_to_path_scroll_out_of_bounds"));
34
35        if self.filesystem.is_dir(selected.path()) {
36            Self::open_dir(popup, selected.path(), &mut self.filesystem)?;
37        } else {
38            self.open_file(selected.path(), terminal)?;
39            *popup = None;
40        }
41
42        Ok(())
43    }
44
45    pub(in crate::app) fn get_current_dir(&self) -> String {
46        let current_path = self.filesystem.pwd();
47        if self.filesystem.is_dir(current_path) {
48            current_path.to_owned()
49        } else {
50            path::parent(current_path)
51                .expect(&t!("errors.file_no_parent"))
52                .to_owned()
53        }
54    }
55
56    pub(in crate::app) fn find_dir_contents(
57        currently_open_path: &str,
58        path: &str,
59        filesystem: &FileSystem,
60    ) -> Result<Vec<PathResult>, Box<dyn Error>> {
61        let mut ret = Vec::new();
62        let (selected_dir, file_name) = if path::is_absolute(path) {
63            if filesystem.is_dir(path) {
64                (filesystem.canonicalize(path)?, "".to_string())
65            } else if let Some(parent) = path::parent(path) {
66                if filesystem.is_dir(parent) {
67                    (
68                        filesystem.canonicalize(parent)?,
69                        path::filename(path).map_or("".into(), |name| name.to_string()),
70                    )
71                } else {
72                    (currently_open_path.to_string(), path.to_string())
73                }
74            } else {
75                (currently_open_path.to_string(), path.to_string())
76            }
77        } else {
78            (currently_open_path.to_string(), path.to_string())
79        };
80
81        let entries = filesystem.ls(&selected_dir)?;
82        let entries = entries
83            .into_iter()
84            .map(|entry| path::diff(&entry, &selected_dir).to_string())
85            .collect::<Vec<_>>();
86
87        let entries = entries
88            .into_iter()
89            .filter(|entry| entry.to_lowercase().starts_with(&file_name.to_lowercase()));
90
91        for entry in entries {
92            if let Ok(result) = PathResult::new(
93                &path::join(&selected_dir, &entry, filesystem.separator()),
94                filesystem,
95            ) {
96                ret.push(result);
97            }
98        }
99
100        Ok(ret)
101    }
102
103    pub(in crate::app) fn open_dir(
104        popup: &mut Option<PopupState>,
105        path: &str,
106        filesystem: &mut FileSystem,
107    ) -> Result<(), Box<dyn Error>> {
108        let path = filesystem.canonicalize(path)?;
109        *popup = Some(PopupState::Open {
110            currently_open_path: path.clone(),
111            path: "".into(),
112            cursor: 0,
113            results: Self::find_dir_contents(&path, "", filesystem)?,
114            scroll: 0,
115        });
116        Ok(())
117    }
118
119    pub fn log_header_info(&mut self) {
120        if self.header != Header::None {
121            match &self.header {
122                Header::GenericHeader(header) => self.log(
123                    NotificationLevel::Info,
124                    t!("app.messages.file_type", file_type = header.file_type() : {:?}),
125                ),
126                // TODO: maybe add info for a more detailed log
127                Header::CustomHeader(_) => self.log(
128                    NotificationLevel::Info,
129                    t!("app.messages.file_type_custom",),
130                ),
131                Header::None => unreachable!(),
132            }
133            self.log(
134                NotificationLevel::Info,
135                t!("app.messages.architecture", architecture = self.header.architecture() : {:?}),
136            );
137            self.log(
138                NotificationLevel::Info,
139                t!("app.messages.bitness", bitness = self.header.bitness()),
140            );
141            self.log(
142                NotificationLevel::Info,
143                t!("app.messages.entry_point", entry_point = self.header.entry_point() : {:#X}),
144            );
145            for section in self.header.get_sections() {
146                self.log(
147                    NotificationLevel::Info,
148                    t!("app.messages.section", section = section),
149                );
150            }
151        } else {
152            self.log(NotificationLevel::Info, t!("app.messages.no_header"));
153        }
154
155        self.log(
156            NotificationLevel::Info,
157            t!(
158                "app.messages.press_for_help",
159                key = Self::key_event_to_string(self.settings.key.help)
160            ),
161        );
162    }
163
164    pub(in crate::app) fn open_file<B: Backend>(
165        &mut self,
166        path: &str,
167        terminal: &mut Terminal<B>,
168    ) -> Result<(), Box<dyn Error>> {
169        self.log(
170            NotificationLevel::Info,
171            t!("app.messages.opening_file", path = path),
172        );
173
174        self.filesystem.cd(path);
175        self.info_mode = InfoMode::Text;
176        self.scroll = 0;
177        self.cursor = (0, 0);
178
179        self.screen_size = Self::get_size(terminal)?;
180        self.block_size = 8;
181        self.vertical_margin = 2;
182        self.blocks_per_row = Self::calc_blocks_per_row(
183            self.block_size,
184            self.screen_size.0,
185            self.fullscreen,
186            self.selected_pane,
187        );
188
189        Self::print_loading_status(
190            &self.settings.color,
191            &t!("app.messages.opening_path", path = path),
192            terminal,
193        )?;
194        self.data = Data::new(
195            self.filesystem.read(self.filesystem.pwd())?,
196            self.settings.app.history_limit,
197        );
198
199        self.load_comments(None);
200
201        Self::print_loading_status(
202            &self.settings.color,
203            &t!("app.messages.decoding_binary"),
204            terminal,
205        )?;
206
207        self.header = self.parse_header();
208
209        Self::print_loading_status(
210            &self.settings.color,
211            &t!("app.messages.disassembling_executable"),
212            terminal,
213        )?;
214
215        (self.assembly_offsets, self.assembly_instructions) =
216            Self::sections_from_bytes(self.data.bytes(), &self.header);
217
218        Self::print_loading_status(
219            &self.settings.color,
220            &t!("app.messages.opening_ui"),
221            terminal,
222        )?;
223        self.log_header_info();
224        let mut app_context = get_app_context!(self);
225        self.plugin_manager.on_open(&mut app_context);
226
227        Ok(())
228    }
229
230    pub(in crate::app) fn save_file_as(&mut self, path: &str) -> Result<(), Box<dyn Error>> {
231        if let Some(parent) = path::parent(path) {
232            self.filesystem.mkdirs(parent)?;
233        };
234
235        self.filesystem.create(path)?;
236        self.filesystem.cd(&self.filesystem.canonicalize(path)?);
237        self.save_file()?;
238        Ok(())
239    }
240
241    pub(in crate::app) fn save_file(&mut self) -> Result<(), Box<dyn Error>> {
242        let mut app_context = get_app_context!(self);
243        self.plugin_manager.on_save(&mut app_context);
244        self.filesystem
245            .write(self.filesystem.pwd(), self.data.bytes())?;
246        self.data.reset_dirty();
247        match &self.filesystem {
248            FileSystem::Local { path } => {
249                self.log(
250                    NotificationLevel::Info,
251                    t!("app.messages.saved_to", path = path),
252                );
253            }
254            FileSystem::Remote { path, connection } => {
255                self.log(
256                    NotificationLevel::Info,
257                    t!("app.messages.saved_to_ssh", path = path, ssh = connection),
258                );
259            }
260        }
261
262        Ok(())
263    }
264}