file_browser/
file_browser.rs1use {
2 cursive::{style::*, utils::span::*, view::*, views::*, *},
3 cursive_tree::*,
4 std::{env::*, fs::*, io, path::*, sync::*, time::*},
5};
6
7fn main() {
14 let mut cursive = Cursive::default();
15
16 let current_dir = current_dir().unwrap();
17 let mut tree = FileBackend::tree_view(current_dir.into());
18
19 tree.model.populate(Some(1)).unwrap();
21
22 let mut browser = LinearLayout::horizontal();
23 browser.add_child(tree.with_name("tree").scrollable().full_screen());
24 browser.add_child(TextView::new("").with_name("details").scrollable().full_screen());
25
26 cursive.add_fullscreen_layer(browser);
27 cursive.add_global_callback('q', |cursive| cursive.quit());
28
29 cursive.run();
30}
31
32struct FileBackend;
33
34impl TreeBackend<Arc<PathBuf>, io::Error, PathBuf, Metadata> for FileBackend {
44 fn roots(
45 base_directory: Arc<PathBuf>,
46 ) -> Result<NodeList<Self, Arc<PathBuf>, io::Error, PathBuf, Metadata>, io::Error> {
47 node_list(0, base_directory.as_ref())
48 }
49
50 fn populate(
51 node: &mut Node<Self, Arc<PathBuf>, io::Error, PathBuf, Metadata>,
52 _base_directory: Arc<PathBuf>,
53 ) -> Result<(), io::Error> {
54 node.children = Some(node_list(node.depth + 1, &node.id)?);
55 Ok(())
56 }
57
58 fn data(
59 node: &mut Node<Self, Arc<PathBuf>, io::Error, PathBuf, Metadata>,
60 _base_directory: Arc<PathBuf>,
61 ) -> Result<Option<(Metadata, bool)>, io::Error> {
62 Ok(Some((node.id.metadata()?, true)))
64 }
65
66 fn handle_selection_changed(cursive: &mut Cursive) {
67 let content = match cursive.call_on_name(
68 "tree",
69 |tree: &mut TreeView<Self, Arc<PathBuf>, io::Error, PathBuf, Metadata>| {
70 let base_directory = tree.model.context.clone();
72 Ok(match tree.selected_node_mut() {
73 Some(node) => match node.data(base_directory)? {
74 Some(metadata) => Some(format_metadata(&metadata)?),
75 None => None,
76 },
77 None => None,
78 })
79 },
80 ) {
81 Some(Ok(Some(text))) => text,
82 Some(Err(error)) => return Self::handle_error(cursive, error),
83 _ => "".into(),
84 };
85
86 cursive.call_on_name("details", |details: &mut TextView| {
87 details.set_content(content);
88 });
89 }
90
91 fn handle_error(cursive: &mut Cursive, error: io::Error) {
92 cursive.add_layer(Dialog::around(TextView::new(error.to_string())).title("I/O Error").button(
94 "OK",
95 |cursive| {
96 cursive.pop_layer();
97 },
98 ));
99 }
100}
101
102fn node_list(
103 depth: usize,
104 directory: &PathBuf,
105) -> Result<NodeList<FileBackend, Arc<PathBuf>, io::Error, PathBuf, Metadata>, io::Error> {
106 let mut list = NodeList::default();
107
108 for entry in read_dir(directory)? {
109 let entry = entry?;
110
111 let kind = if entry.file_type()?.is_dir() { NodeKind::Branch } else { NodeKind::Leaf };
112
113 let file_name = entry.file_name();
114 let file_name = file_name.to_string_lossy();
115
116 let mut representation = Representation::default();
118 if kind.is_branch() {
119 representation.append_styled(file_name, Style::title_primary());
120 } else {
121 representation.append_styled(file_name, Style::title_secondary());
122 }
123
124 list.add(depth, kind, entry.path(), representation);
125 }
126
127 Ok(list)
128}
129
130fn format_metadata(metadata: &Metadata) -> Result<Representation, io::Error> {
131 let mut text = Default::default();
132
133 append_field(&mut text, "Created", &format_system_time(metadata.created()?));
134 append_field(&mut text, "Modified", &format_system_time(metadata.modified()?));
135 append_field(&mut text, "Accessed", &format_system_time(metadata.accessed()?));
136 text.append('\n');
137 append_field(&mut text, "Read-only", &metadata.permissions().readonly().to_string());
138
139 Ok(text.canonical())
140}
141
142fn append_field(text: &mut SpannedString<Style>, field: &str, value: &str) {
143 text.append_styled(field, Style::title_secondary());
144 text.append(": ");
145 text.append(value);
146 text.append('\n');
147}
148
149fn format_system_time(system_time: SystemTime) -> String {
150 format!("{} minutes ago", system_time.elapsed().unwrap().as_secs() / 60)
151}