liner/
history.rs

1use super::*;
2
3use std::{
4    collections::{vec_deque, VecDeque},
5    fs::File,
6    io::{self, Write},
7    io::{BufRead, BufReader, BufWriter},
8    iter::IntoIterator,
9    ops::Index,
10    ops::IndexMut,
11    path::Path,
12    //time::Duration,
13};
14
15const DEFAULT_MAX_SIZE: usize = 1000;
16
17/// Structure encapsulating command history
18pub struct History {
19    // TODO: this should eventually be private
20    /// Vector of buffers to store history in
21    pub buffers: VecDeque<Buffer>,
22    /// Store a filename to save history into; if None don't save history
23    file_name: Option<String>,
24    /// Maximal number of buffers stored in the memory
25    /// TODO: just make this public?
26    max_buffers_size: usize,
27    /// Maximal number of lines stored in the file
28    // TODO: just make this public?
29    max_file_size: usize,
30    // TODO set from environment variable?
31    pub append_duplicate_entries: bool,
32    /// Append each entry to history file as entered?
33    pub inc_append: bool,
34    /// Share history across ion's with the same history file (combine with inc_append).
35    pub share: bool,
36    /// Last filesize of history file, used to optimize history sharing.
37    pub file_size: u64,
38    /// Allow loading duplicate entries, need to know this for loading history files.
39    pub load_duplicates: bool,
40    /// Writes between history compaction.
41    compaction_writes: usize,
42}
43
44impl Default for History {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl History {
51    /// Create new History structure.
52    pub fn new() -> History {
53        History {
54            buffers: VecDeque::with_capacity(DEFAULT_MAX_SIZE),
55            file_name: None,
56            max_buffers_size: DEFAULT_MAX_SIZE,
57            max_file_size: DEFAULT_MAX_SIZE,
58            append_duplicate_entries: false,
59            inc_append: false,
60            share: false,
61            file_size: 0,
62            load_duplicates: true,
63            compaction_writes: 0,
64        }
65    }
66
67    /// Clears out the history.
68    pub fn clear_history(&mut self) {
69        self.buffers.clear();
70    }
71
72    /// Loads the history file from the saved path and appends it to the end of the history if append
73    /// is true otherwise replace history.
74    pub fn load_history(&mut self, append: bool) -> io::Result<u64> {
75        if let Some(path) = self.file_name.clone() {
76            let file_size = self.file_size;
77            self.load_history_file_test(&path, file_size, append)
78                .inspect(|&l| {
79                    self.file_size = l;
80                })
81        } else {
82            Err(io::Error::new(
83                io::ErrorKind::Other,
84                "History filename not set!",
85            ))
86        }
87    }
88
89    /// Loads the history file from path and appends it to the end of the history if append is true.
90    pub fn load_history_file<P: AsRef<Path>>(&mut self, path: P, append: bool) -> io::Result<u64> {
91        self.load_history_file_test(path, 0, append)
92    }
93
94    /// Loads the history file from path and appends it to the end of the history.f append is true
95    /// (replaces if false).  Only loads if length is not equal to current file size.
96    fn load_history_file_test<P: AsRef<Path>>(
97        &mut self,
98        path: P,
99        length: u64,
100        append: bool,
101    ) -> io::Result<u64> {
102        let path = path.as_ref();
103        let file = if path.exists() {
104            File::open(path)?
105        } else {
106            let status = format!("File not found {:?}", path);
107            return Err(io::Error::new(io::ErrorKind::Other, status));
108        };
109        let new_length = file.metadata()?.len();
110        if new_length == 0 && length == 0 && !append {
111            // Special case, trying to load nothing and not appending- just clear.
112            self.clear_history();
113        }
114        if new_length != length {
115            if !append {
116                self.clear_history();
117            }
118            let reader = BufReader::new(file);
119            for line in reader.lines() {
120                match line {
121                    Ok(line) => {
122                        if !line.starts_with('#') {
123                            self.buffers.push_back(Buffer::from(line));
124                        }
125                    }
126                    Err(_) => break,
127                }
128            }
129            self.truncate();
130            if !self.load_duplicates {
131                let mut tmp_buffers: Vec<Buffer> = Vec::with_capacity(self.buffers.len());
132                // Remove duplicates from loaded history if we do not want it.
133                while let Some(buf) = self.buffers.pop_back() {
134                    self.remove_duplicates(&buf.to_string()[..]);
135                    tmp_buffers.push(buf);
136                }
137                while let Some(buf) = tmp_buffers.pop() {
138                    self.buffers.push_back(buf);
139                }
140            }
141        }
142        Ok(new_length)
143    }
144
145    /// Removes duplicates and trims a history file to max_file_size.
146    /// Primarily if inc_append is set without shared history.
147    /// Static because it should have no side effects on a history object.
148    fn deduplicate_history_file<P: AsRef<Path>>(
149        path: P,
150        max_file_size: usize,
151    ) -> io::Result<String> {
152        let path = path.as_ref();
153        let file = if path.exists() {
154            File::open(path)?
155        } else {
156            let status = format!("File not found {:?}", path);
157            return Err(io::Error::new(io::ErrorKind::Other, status));
158        };
159        let mut buf: VecDeque<String> = VecDeque::new();
160        let reader = BufReader::new(file);
161        for line in reader.lines() {
162            match line {
163                Ok(line) => {
164                    if !line.starts_with('#') {
165                        buf.push_back(line);
166                    }
167                }
168                Err(_) => break,
169            }
170        }
171        let org_length = buf.len();
172        if buf.len() >= max_file_size {
173            let pop_out = buf.len() - max_file_size;
174            for _ in 0..pop_out {
175                buf.pop_front();
176            }
177        }
178        let mut tmp_buffers: Vec<String> = Vec::with_capacity(buf.len());
179        // Remove duplicates from loaded history if we do not want it.
180        while let Some(line) = buf.pop_back() {
181            buf.retain(|buffer| *buffer != line);
182            tmp_buffers.push(line);
183        }
184        while let Some(line) = tmp_buffers.pop() {
185            buf.push_back(line);
186        }
187
188        if org_length != buf.len() {
189            // Overwrite the history file with the deduplicated version if it changed.
190            let mut file = BufWriter::new(File::create(path)?);
191            // Write the commands to the history file.
192            for command in buf.into_iter() {
193                let _ = file.write_all(command.as_bytes());
194                let _ = file.write_all(b"\n");
195            }
196        }
197        Ok("De-duplicated history file.".to_string())
198    }
199
200    /// Set history file name and at the same time load the history.
201    pub fn set_file_name_and_load_history<P: AsRef<Path>>(&mut self, path: P) -> io::Result<u64> {
202        let path = path.as_ref();
203        self.file_name = path.to_str().map(|s| s.to_owned());
204        self.file_size = 0;
205        if path.exists() {
206            self.load_history_file(path, false).inspect(|&l| {
207                self.file_size = l;
208            })
209        } else {
210            File::create(path)?;
211            Ok(0)
212        }
213    }
214
215    /// Set maximal number of buffers stored in memory
216    pub fn set_max_buffers_size(&mut self, size: usize) {
217        self.max_buffers_size = size;
218    }
219
220    /// Set maximal number of entries in history file
221    pub fn set_max_file_size(&mut self, size: usize) {
222        self.max_file_size = size;
223    }
224
225    /// Number of items in history.
226    #[inline(always)]
227    pub fn len(&self) -> usize {
228        self.buffers.len()
229    }
230
231    /// Is the history empty
232    pub fn is_empty(&self) -> bool {
233        self.buffers.is_empty()
234    }
235
236    /// Add a command to the history buffer and remove the oldest commands when the max history
237    /// size has been met. If writing to the disk is enabled, this function will be used for
238    /// logging history to the designated history file.
239    pub fn push(&mut self, new_item: Buffer) -> io::Result<()> {
240        // buffers[0] is the oldest entry
241        // the new entry goes to the end
242        if !self.append_duplicate_entries
243            && self.buffers.back().map(|b| b.to_string()) == Some(new_item.to_string())
244        {
245            return Ok(());
246        }
247
248        let item_str = String::from(new_item.clone());
249        self.buffers.push_back(new_item);
250        //self.to_max_size();
251        while self.buffers.len() > self.max_buffers_size {
252            self.buffers.pop_front();
253        }
254
255        if self.inc_append && self.file_name.is_some() {
256            if !self.load_duplicates {
257                // Do not want duplicates so periodically compact the history file.
258                self.compaction_writes += 1;
259                // Every 30 writes "compact" the history file by writing just in memory history.  This
260                // is to keep the history file clean and at a reasonable size (not much over max
261                // history size at it's worst).
262                if self.compaction_writes > 29 {
263                    if self.share {
264                        // Reload history, we may be out of sync.
265                        let _ = self.load_history(false);
266                        // Commit the duplicated history.
267                        if let Some(file_name) = self.file_name.clone() {
268                            let _ = self.overwrite_history(file_name);
269                        }
270                    } else {
271                        // Not using shared history so just de-dup the file without messing with
272                        // our history.
273                        if let Some(file_name) = self.file_name.clone() {
274                            let _ =
275                                History::deduplicate_history_file(file_name, self.max_file_size);
276                        }
277                    }
278                    self.compaction_writes = 0;
279                }
280            } else {
281                // If allowing duplicates then no need for compaction.
282                self.compaction_writes = 1;
283            }
284            let file_name = self.file_name.clone().unwrap();
285            if let Ok(inner_file) = std::fs::OpenOptions::new().append(true).open(&file_name) {
286                // Leave file size alone, if it is not right trigger a reload later.
287                if self.compaction_writes > 0 {
288                    // If 0 we "compacted" and nothing to write.
289                    let mut file = BufWriter::new(inner_file);
290                    let _ = file.write_all(item_str.as_bytes());
291                    let _ = file.write_all(b"\n");
292                    // Save the filesize after each append so we do not reload when we do not need to.
293                    self.file_size += item_str.len() as u64 + 1;
294                }
295            }
296        }
297        Ok(())
298    }
299
300    /// Removes duplicate entries in the history
301    pub fn remove_duplicates(&mut self, input: &str) {
302        self.buffers.retain(|buffer| {
303            let command = buffer.lines().concat();
304            command != input
305        });
306    }
307
308    fn get_match<I>(&self, vals: I, search_term: &Buffer) -> Option<usize>
309    where
310        I: Iterator<Item = usize>,
311    {
312        vals.filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
313            .find(|(_i, tested)| tested.starts_with(search_term))
314            .map(|(i, _)| i)
315    }
316
317    /// Go through the history and try to find an index (newest to oldest) which starts the same
318    /// as the new buffer given to this function as argument.  Starts at curr_position.  Does no wrap.
319    pub fn get_newest_match(
320        &self,
321        curr_position: Option<usize>,
322        new_buff: &Buffer,
323    ) -> Option<usize> {
324        let pos = curr_position.unwrap_or(self.buffers.len());
325        if pos > 0 {
326            self.get_match((0..pos).rev(), new_buff)
327        } else {
328            None
329        }
330    }
331
332    pub fn get_history_subset(&self, search_term: &Buffer) -> Vec<usize> {
333        let mut v: Vec<usize> = Vec::new();
334        let mut ret: Vec<usize> = (0..self.len())
335            .filter(|i| {
336                if let Some(tested) = self.buffers.get(*i) {
337                    let starts = tested.starts_with(search_term);
338                    let contains = tested.contains(search_term);
339                    if starts {
340                        v.push(*i);
341                    }
342                    contains && !starts && tested != search_term
343                } else {
344                    false
345                }
346            })
347            .collect();
348        ret.append(&mut v);
349        ret
350    }
351
352    pub fn search_index(&self, search_term: &Buffer) -> Vec<usize> {
353        (0..self.len())
354            .filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
355            .filter(|(_i, tested)| tested.contains(search_term))
356            .map(|(i, _)| i)
357            .collect()
358    }
359
360    /// Get the history file name.
361    #[inline(always)]
362    pub fn file_name(&self) -> Option<&str> {
363        self.file_name.as_deref()
364    }
365
366    fn truncate(&mut self) {
367        // Find how many lines we need to move backwards
368        // in the file to remove all the old commands.
369        if self.buffers.len() >= self.max_file_size {
370            let pop_out = self.buffers.len() - self.max_file_size;
371            for _ in 0..pop_out {
372                self.buffers.pop_front();
373            }
374        }
375    }
376
377    fn overwrite_history<P: AsRef<Path>>(&mut self, path: P) -> io::Result<String> {
378        self.truncate();
379        let mut file = BufWriter::new(File::create(&path)?);
380
381        // Write the commands to the history file.
382        for command in self.buffers.iter().cloned() {
383            let _ = file.write_all(String::from(command).as_bytes());
384            let _ = file.write_all(b"\n");
385        }
386        Ok("Wrote history to file.".to_string())
387    }
388
389    pub fn commit_to_file_path<P: AsRef<Path>>(&mut self, path: P) -> io::Result<String> {
390        if self.inc_append {
391            Ok("Nothing to commit.".to_string())
392        } else {
393            self.overwrite_history(path)
394        }
395    }
396
397    pub fn commit_to_file(&mut self) {
398        if let Some(file_name) = self.file_name.clone() {
399            let _ = self.commit_to_file_path(file_name);
400        }
401    }
402}
403
404impl<'a> IntoIterator for &'a History {
405    type Item = &'a Buffer;
406    type IntoIter = vec_deque::Iter<'a, Buffer>;
407
408    fn into_iter(self) -> Self::IntoIter {
409        self.buffers.iter()
410    }
411}
412
413impl Index<usize> for History {
414    type Output = Buffer;
415
416    fn index(&self, index: usize) -> &Buffer {
417        &self.buffers[index]
418    }
419}
420
421impl IndexMut<usize> for History {
422    fn index_mut(&mut self, index: usize) -> &mut Buffer {
423        &mut self.buffers[index]
424    }
425}