fm/modes/display/
directory.rs1use std::borrow::Borrow;
2use std::collections::{BTreeSet, VecDeque};
3use std::fs::read_dir;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use anyhow::{Context, Result};
8
9use crate::app::TabSettings;
10use crate::io::git;
11use crate::modes::{is_not_hidden, path_is_video, FileInfo, FileKind, FilterKind, SortKind, Users};
12use crate::{impl_content, impl_index_to_index, impl_selectable, log_info};
13
14pub struct Directory {
18 pub path: Arc<Path>,
20 pub content: Vec<FileInfo>,
22 pub index: usize,
24 used_space: u64,
25}
26
27impl Directory {
28 pub fn new(path: &Path, users: &Users, filter: &FilterKind, show_hidden: bool) -> Result<Self> {
32 let path = Arc::from(path);
33 let mut content = Self::files(&path, show_hidden, filter, users)?;
34 let sort_kind = SortKind::default();
35 sort_kind.sort(&mut content);
36 let index: usize = 0;
37 let used_space = get_used_space(&content);
38
39 Ok(Self {
40 path,
41 content,
42 index,
43 used_space,
44 })
45 }
46
47 pub fn change_directory(
48 &mut self,
49 path: &Path,
50 settings: &TabSettings,
51 users: &Users,
52 ) -> Result<()> {
53 self.content = Self::files(path, settings.show_hidden, &settings.filter, users)?;
54 settings.sort_kind.sort(&mut self.content);
55 self.index = 0;
56 self.used_space = get_used_space(&self.content);
57 self.path = Arc::from(path);
58 Ok(())
59 }
60
61 fn files(
62 path: &Path,
63 show_hidden: bool,
64 filter_kind: &FilterKind,
65 users: &Users,
66 ) -> Result<Vec<FileInfo>> {
67 let mut files: Vec<FileInfo> = Self::create_dot_dotdot(path, users)?;
68 if let Some(true_files) = files_collection(path, users, show_hidden, filter_kind, false) {
69 files.extend(true_files);
70 }
71 Ok(files)
72 }
73
74 fn create_dot_dotdot(path: &Path, users: &Users) -> Result<Vec<FileInfo>> {
75 let current = FileInfo::from_path_with_name(path, ".", users)?;
76 let Some(parent) = path.parent() else {
77 return Ok(vec![current]);
78 };
79 let parent = FileInfo::from_path_with_name(parent, "..", users)?;
80 Ok(vec![current, parent])
81 }
82
83 pub fn sort(&mut self, sort_kind: &SortKind) {
85 sort_kind.sort(&mut self.content)
86 }
87
88 pub fn owner_column_width(&self) -> usize {
90 let owner_size_btreeset: BTreeSet<usize> =
91 self.iter().map(|file| file.owner.len()).collect();
92 *owner_size_btreeset.iter().next_back().unwrap_or(&1)
93 }
94
95 pub fn group_column_width(&self) -> usize {
97 let group_size_btreeset: BTreeSet<usize> =
98 self.iter().map(|file| file.group.len()).collect();
99 *group_size_btreeset.iter().next_back().unwrap_or(&1)
100 }
101
102 pub fn select_index(&mut self, index: usize) {
104 if index < self.content.len() {
105 self.index = index;
106 }
107 }
108
109 pub fn reset_files(&mut self, settings: &TabSettings, users: &Users) -> Result<()> {
113 self.content = Self::files(&self.path, settings.show_hidden, &settings.filter, users)?;
114 self.sort(&settings.sort_kind);
115 self.index = 0;
116 Ok(())
117 }
118
119 pub fn is_selected_dir(&self) -> Result<bool> {
122 let fileinfo = self.selected().context("")?;
123 match fileinfo.file_kind {
124 FileKind::Directory => Ok(true),
125 FileKind::SymbolicLink(true) => {
126 let dest = std::fs::read_link(&fileinfo.path).unwrap_or_default();
127 Ok(dest.is_dir())
128 }
129 _ => Ok(false),
130 }
131 }
132
133 pub fn used_space(&self) -> String {
137 human_size(self.used_space)
138 }
139
140 pub fn git_string(&self) -> Result<String> {
142 git(&self.path)
143 }
144
145 #[inline]
147 pub fn iter(&self) -> std::slice::Iter<'_, FileInfo> {
148 self.content.iter()
149 }
150
151 #[inline]
153 pub fn enumerate(&self) -> Enumerate<std::slice::Iter<'_, FileInfo>> {
154 self.iter().enumerate()
155 }
156
157 fn find_jump_index(&self, jump_target: &Path) -> Option<usize> {
159 self.content
160 .iter()
161 .position(|file| <Arc<Path> as Borrow<Path>>::borrow(&file.path) == jump_target)
162 }
163
164 pub fn select_file(&mut self, jump_target: &Path) -> usize {
166 let index = self.find_jump_index(jump_target).unwrap_or_default();
167 self.select_index(index);
168 index
169 }
170
171 pub fn paths(&self) -> Vec<&Path> {
173 self.content
174 .iter()
175 .map(|fileinfo| fileinfo.path.borrow())
176 .collect()
177 }
178
179 pub fn is_dotdot_selected(&self) -> bool {
181 let Some(selected) = &self.selected() else {
182 return false;
183 };
184 let Some(parent) = self.path.parent() else {
185 return false;
186 };
187 selected.path.as_ref() == parent
188 }
189
190 pub fn videos(&self) -> VecDeque<PathBuf> {
191 self.content()
192 .iter()
193 .map(|f| f.path.to_path_buf())
194 .filter(|p| path_is_video(p))
195 .collect()
196 }
197}
198
199impl_index_to_index!(FileInfo, Directory);
200impl_selectable!(Directory);
201impl_content!(Directory, FileInfo);
202
203fn get_used_space(files: &[FileInfo]) -> u64 {
204 files
205 .iter()
206 .filter(|f| !f.is_dir())
207 .map(|f| f.true_size)
208 .sum()
209}
210
211pub fn files_collection(
215 path: &Path,
216 users: &Users,
217 show_hidden: bool,
218 filter_kind: &FilterKind,
219 keep_dir: bool,
220) -> Option<Vec<FileInfo>> {
221 match read_dir(path) {
222 Ok(read_dir) => Some(
223 read_dir
224 .filter_map(|direntry| direntry.ok())
225 .filter(|direntry| show_hidden || is_not_hidden(direntry).unwrap_or(true))
226 .filter_map(|direntry| FileInfo::from_direntry(&direntry, users).ok())
227 .filter(|fileinfo| filter_kind.filter_by(fileinfo, keep_dir))
228 .collect(),
229 ),
230 Err(error) => {
231 log_info!("Couldn't read path {path} - {error}", path = path.display(),);
232 None
233 }
234 }
235}
236
237const SIZES: [&str; 9] = ["B", "k", "M", "G", "T", "P", "E", "Z", "Y"];
238
239#[inline]
241pub fn human_size(bytes: u64) -> String {
242 let mut factor = 0;
243 let mut size = bytes as f64;
244
245 while size >= 1000.0 && factor < SIZES.len() - 1 {
246 size /= 1000.0;
247 factor += 1;
248 }
249
250 if size < 9.5 && factor > 0 {
251 format!("{size:.1}{unit}", unit = SIZES[factor])
252 } else {
253 format!(
254 "{size:>3}{unit}",
255 size = size.round() as u64,
256 unit = SIZES[factor]
257 )
258 }
259}