datman/commands/
ilabel.rs1use std::io;
20use std::io::{StdinLock, Stdout, Write};
21use std::path::Path;
22
23use arc_interner::ArcIntern;
24use byteorder::ReadBytesExt;
25use termion::input::TermRead;
26use termion::raw::{IntoRawMode, RawTerminal};
27
28use crate::descriptor::{load_descriptor, Descriptor, SourceDescriptor};
29use crate::labelling::State::{Excluded, Labelled, Split};
30use crate::labelling::{
31 load_labelling_rules, save_labelling_rules, GlobRule, Label, LabellingRules, State,
32};
33use crate::tree::{scan, FileTree, FileTree1};
34
35use anyhow::{anyhow, bail};
36
37pub fn interactive_label_node(
38 path: String,
39 current_state: Option<State>,
40 node: &mut FileTree1<Option<State>>,
41 labels: &Vec<Label>,
42 rules: &mut LabellingRules,
43 stdin: &mut StdinLock,
44 stdout: &mut RawTerminal<Stdout>,
45) -> anyhow::Result<()> {
46 let mut next_state = current_state;
47 if let Some(rule_state) = rules.apply(&path) {
48 next_state = Some(rule_state.clone());
49 } else if !next_state
50 .as_ref()
51 .map(|s| s.should_inherit())
52 .unwrap_or(false)
53 {
54 if node.is_dir() {
55 stdout.write_all(format!("\r{}/: _", path).as_bytes())?;
56 } else if node.is_symlink() {
57 stdout.write_all(format!("\r{} (symlink): _", path).as_bytes())?;
58 } else {
59 stdout.write_all(format!("\r{}: _", path).as_bytes())?;
60 }
61 stdout.flush()?;
62 let user_input_state = loop {
63 let next_char = stdin.read_u8()? as char;
64 if next_char >= '1' && next_char <= '9' {
65 let index = next_char as usize - '1' as usize;
66 if let Some(label) = labels.get(index) {
67 rules
68 .position_based_rules
69 .insert(path.clone(), Labelled(label.clone()));
70 print!("\x08{}\r\n", label.0);
71 break Some(Labelled(label.clone()));
72 }
73 } else if next_char == 'x' {
74 rules.position_based_rules.insert(path.clone(), Excluded);
75 print!("\x08{}\r\n", next_char);
76 break Some(Excluded);
77 } else if next_char == 's' {
78 if node.is_dir() {
79 rules.position_based_rules.insert(path.clone(), Split);
80 print!("\x08{}\r\n", next_char);
81 break Some(Split);
82 } else {
83 print!("\x08!");
84 stdout.flush()?;
85 }
86 } else if next_char == 'p' {
87 print!("\x08p\r\n\tPattern mode. Choose a label or other effect to apply to the pattern matches: _");
88 stdout.flush()?;
89
90 let rule_apply_state = loop {
91 let next_char = stdin.read_u8()? as char;
92 if next_char >= '1' && next_char <= '9' {
93 let index = next_char as usize - '1' as usize;
94 if let Some(label) = labels.get(index) {
95 print!("\x08{}\r\n", label.0);
96 break Labelled(label.clone());
97 }
98 } else if next_char == 'x' {
99 print!("\x08{}\r\n", next_char);
100 break Excluded;
101 } else if next_char == 's' {
102 print!("\x08{}\r\n", next_char);
103 break Split;
104 }
105 };
106 stdout.flush()?;
107
108 stdout.suspend_raw_mode()?;
109 print!("\tEnter a glob pattern to match on:\n\t");
110 stdout.flush()?;
111 let (pattern, glob) = loop {
112 let pattern = stdin
113 .read_line()?
114 .ok_or_else(|| anyhow!("EOT? when reading glob pattern"))?;
115
116 match glob::Pattern::new(&pattern) {
117 Ok(glob) => {
118 if !glob.matches(&path) {
119 println!("Doesn't match the path in question.");
120 continue;
121 }
122 break (pattern, glob);
123 }
124 Err(error) => {
125 println!("Error: {:?}. Try again.", error);
126 }
127 }
128 };
129 stdout.activate_raw_mode()?;
130
131 rules.glob_based_rules.push(GlobRule {
132 pattern,
133 glob,
134 outcome: rule_apply_state.clone(),
135 });
136 break Some(rule_apply_state);
137 }
138 };
139 next_state = user_input_state;
140 }
141
142 match node {
143 FileTree::NormalFile { meta, .. } => {
144 *meta = next_state;
145 }
146 FileTree::Directory { meta, children, .. } => {
147 *meta = next_state.clone();
148
149 for (child_name, child) in children.iter_mut() {
150 let child_path = format!("{}/{}", path, child_name);
151 interactive_label_node(
152 child_path,
153 next_state.clone(),
154 child,
155 labels,
156 rules,
157 stdin,
158 stdout,
159 )?;
160 }
161 }
162 FileTree::SymbolicLink { meta, .. } => {
163 *meta = next_state;
164 }
165 FileTree::Other(_) => {
166 panic!("Other() nodes shouldn't be present here.");
167 }
168 }
169
170 Ok(())
171}
172
173pub fn interactive_labelling_session(path: &Path, source_name: String) -> anyhow::Result<()> {
174 let descriptor: Descriptor = load_descriptor(path)?;
175
176 let source = descriptor
177 .source
178 .get(&source_name)
179 .ok_or_else(|| anyhow!("No source found by that name!"))?;
180
181 if let SourceDescriptor::DirectorySource {
182 hostname: _,
183 directory,
184 } = source
185 {
186 println!("Scanning source; this might take a little while...");
187 let mut dir_scan = scan(directory)?
188 .ok_or_else(|| anyhow!("Empty source."))?
189 .replace_meta(&None);
190
191 let mut rules = load_labelling_rules(path, &source_name)?;
192
193 let labels: Vec<Label> = descriptor
194 .labels
195 .iter()
196 .map(|label| Label(ArcIntern::new(label.clone())))
197 .collect();
198
199 println!("The following label mappings are available:");
200 for (idx, label) in labels.iter().enumerate() {
201 println!("\tFor {:?}, press {}!", label.0.as_ref(), idx + 1);
202 }
203 println!("\tTo split a directory, press 's'!");
204 println!("\tTo exclude an entry, press 'x'!");
205 println!("\tTo apply a pattern, press 'p'...");
206
207 let mut stdout = io::stdout().into_raw_mode().unwrap();
209 let stdin_unlocked = io::stdin();
210 let mut stdin = stdin_unlocked.lock();
211 interactive_label_node(
212 "".to_owned(),
213 None,
214 &mut dir_scan,
215 &labels,
216 &mut rules,
217 &mut stdin,
218 &mut stdout,
219 )?;
220 drop(stdout);
221 drop(stdin);
222
223 println!("\nLabelling completed!");
224
225 save_labelling_rules(path, &source_name, &rules)?;
227 } else {
228 bail!("Can't do interactive labelling on a non-directory source.");
229 }
230
231 Ok(())
232}