history.rs 14.8 KB
Newer Older
1 2
use super::*;

3 4 5
use std::{
    collections::{vec_deque, VecDeque},
    fs::File,
6
    io::{self, Write},
7
    io::{BufRead, BufReader, BufWriter},
8 9 10 11 12 13
    iter::IntoIterator,
    ops::Index,
    ops::IndexMut,
    path::Path,
    //time::Duration,
};
14

15 16
const DEFAULT_MAX_SIZE: usize = 1000;

17 18
/// Structure encapsulating command history
pub struct History {
Sehny's avatar
Sehny committed
19 20
    // TODO: this should eventually be private
    /// Vector of buffers to store history in
Sehny's avatar
Sehny committed
21
    pub buffers: VecDeque<Buffer>,
22
    /// Store a filename to save history into; if None don't save history
Sehny's avatar
Sehny committed
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 31
    // TODO set from environment variable?
    pub append_duplicate_entries: bool,
32 33 34 35 36 37 38 39 40 41
    /// Append each entry to history file as entered?
    pub inc_append: bool,
    /// Share history across ion's with the same history file (combine with inc_append).
    pub share: bool,
    /// Last filesize of history file, used to optimize history sharing.
    pub file_size: u64,
    /// Allow loading duplicate entries, need to know this for loading history files.
    pub load_duplicates: bool,
    /// Writes between history compaction.
    compaction_writes: usize,
42 43
}

AdminXVII's avatar
AdminXVII committed
44 45 46 47 48 49
impl Default for History {
    fn default() -> Self {
        Self::new()
    }
}

50 51 52 53
impl History {
    /// Create new History structure.
    pub fn new() -> History {
        History {
54
            buffers: VecDeque::with_capacity(DEFAULT_MAX_SIZE),
55
            file_name: None,
56 57
            max_buffers_size: DEFAULT_MAX_SIZE,
            max_file_size: DEFAULT_MAX_SIZE,
58
            append_duplicate_entries: false,
59 60 61 62 63
            inc_append: false,
            share: false,
            file_size: 0,
            load_duplicates: true,
            compaction_writes: 0,
64 65 66
        }
    }

67 68 69 70 71 72 73 74 75 76
    /// Clears out the history.
    pub fn clear_history(&mut self) {
        self.buffers.clear();
    }

    /// Loads the history file from the saved path and appends it to the end of the history if append
    /// is true otherwise replace history.
    pub fn load_history(&mut self, append: bool) -> io::Result<u64> {
        if let Some(path) = self.file_name.clone() {
            let file_size = self.file_size;
Steven Stanfield's avatar
Steven Stanfield committed
77 78 79 80 81
            self.load_history_file_test(&path, file_size, append)
                .map(|l| {
                    self.file_size = l;
                    l
                })
82
        } else {
Steven Stanfield's avatar
Steven Stanfield committed
83 84 85 86
            Err(io::Error::new(
                io::ErrorKind::Other,
                "History filename not set!",
            ))
87 88 89 90 91 92 93 94 95 96
        }
    }

    /// Loads the history file from path and appends it to the end of the history if append is true.
    pub fn load_history_file<P: AsRef<Path>>(&mut self, path: P, append: bool) -> io::Result<u64> {
        self.load_history_file_test(path, 0, append)
    }

    /// Loads the history file from path and appends it to the end of the history.f append is true
    /// (replaces if false).  Only loads if length is not equal to current file size.
Steven Stanfield's avatar
Steven Stanfield committed
97 98 99 100 101 102
    fn load_history_file_test<P: AsRef<Path>>(
        &mut self,
        path: P,
        length: u64,
        append: bool,
    ) -> io::Result<u64> {
103 104 105 106
        let path = path.as_ref();
        let file = if path.exists() {
            File::open(path)?
        } else {
107 108
            let status = format!("File not found {:?}", path);
            return Err(io::Error::new(io::ErrorKind::Other, status));
109
        };
110 111 112 113 114 115 116 117 118 119 120 121
        let new_length = file.metadata()?.len();
        if new_length == 0 && length == 0 && !append {
            // Special case, trying to load nothing and not appending- just clear.
            self.clear_history();
        }
        if new_length != length {
            if !append {
                self.clear_history();
            }
            let reader = BufReader::new(file);
            for line in reader.lines() {
                match line {
122 123 124 125 126
                    Ok(line) => {
                        if !line.starts_with('#') {
                            self.buffers.push_back(Buffer::from(line));
                        }
                    }
127 128 129
                    Err(_) => break,
                }
            }
AdminXVII's avatar
AdminXVII committed
130
            self.truncate();
131
            if !self.load_duplicates {
132
                let mut tmp_buffers: Vec<Buffer> = Vec::with_capacity(self.buffers.len());
133 134 135 136 137 138 139 140
                // Remove duplicates from loaded history if we do not want it.
                while let Some(buf) = self.buffers.pop_back() {
                    self.remove_duplicates(&buf.to_string()[..]);
                    tmp_buffers.push(buf);
                }
                while let Some(buf) = tmp_buffers.pop() {
                    self.buffers.push_back(buf);
                }
141 142
            }
        }
143 144 145
        Ok(new_length)
    }

146 147 148
    /// Removes duplicates and trims a history file to max_file_size.
    /// Primarily if inc_append is set without shared history.
    /// Static because it should have no side effects on a history object.
Steven Stanfield's avatar
Steven Stanfield committed
149 150 151 152
    fn deduplicate_history_file<P: AsRef<Path>>(
        path: P,
        max_file_size: usize,
    ) -> io::Result<String> {
153 154 155 156 157 158 159 160 161 162 163
        let path = path.as_ref();
        let file = if path.exists() {
            File::open(path)?
        } else {
            let status = format!("File not found {:?}", path);
            return Err(io::Error::new(io::ErrorKind::Other, status));
        };
        let mut buf: VecDeque<String> = VecDeque::new();
        let reader = BufReader::new(file);
        for line in reader.lines() {
            match line {
164 165
                Ok(line) => {
                    if !line.starts_with('#') {
166
                        buf.push_back(line);
167 168
                    }
                }
169 170 171 172 173 174 175 176 177 178 179 180 181
                Err(_) => break,
            }
        }
        let org_length = buf.len();
        if buf.len() >= max_file_size {
            let pop_out = buf.len() - max_file_size;
            for _ in 0..pop_out {
                buf.pop_front();
            }
        }
        let mut tmp_buffers: Vec<String> = Vec::with_capacity(buf.len());
        // Remove duplicates from loaded history if we do not want it.
        while let Some(line) = buf.pop_back() {
Steven Stanfield's avatar
Steven Stanfield committed
182
            buf.retain(|buffer| *buffer != line);
183 184 185 186 187 188 189 190 191 192 193
            tmp_buffers.push(line);
        }
        while let Some(line) = tmp_buffers.pop() {
            buf.push_back(line);
        }

        if org_length != buf.len() {
            // Overwrite the history file with the deduplicated version if it changed.
            let mut file = BufWriter::new(File::create(&path)?);
            // Write the commands to the history file.
            for command in buf.into_iter() {
AdminXVII's avatar
AdminXVII committed
194
                let _ = file.write_all(&command.as_bytes());
195 196 197 198 199 200
                let _ = file.write_all(b"\n");
            }
        }
        Ok("De-duplicated history file.".to_string())
    }

201 202 203
    /// Set history file name and at the same time load the history.
    pub fn set_file_name_and_load_history<P: AsRef<Path>>(&mut self, path: P) -> io::Result<u64> {
        let path = path.as_ref();
204
        self.file_name = path.to_str().map(|s| s.to_owned());
205 206
        self.file_size = 0;
        if path.exists() {
Steven Stanfield's avatar
Steven Stanfield committed
207 208 209 210
            self.load_history_file(path, false).map(|l| {
                self.file_size = l;
                l
            })
211 212 213 214
        } else {
            File::create(path)?;
            Ok(0)
        }
215 216 217 218 219 220 221 222 223 224 225 226
    }

    /// Set maximal number of buffers stored in memory
    pub fn set_max_buffers_size(&mut self, size: usize) {
        self.max_buffers_size = size;
    }

    /// Set maximal number of entries in history file
    pub fn set_max_file_size(&mut self, size: usize) {
        self.max_file_size = size;
    }

227
    /// Number of items in history.
228
    #[inline(always)]
229
    pub fn len(&self) -> usize {
Sehny's avatar
Sehny committed
230
        self.buffers.len()
231 232
    }

AdminXVII's avatar
AdminXVII committed
233 234 235 236 237
    /// Is the history empty
    pub fn is_empty(&self) -> bool {
        self.buffers.is_empty()
    }

238
    /// Add a command to the history buffer and remove the oldest commands when the max history
239 240
    /// size has been met. If writing to the disk is enabled, this function will be used for
    /// logging history to the designated history file.
241
    pub fn push(&mut self, new_item: Buffer) -> io::Result<()> {
242 243
        // buffers[0] is the oldest entry
        // the new entry goes to the end
MovingtoMars's avatar
MovingtoMars committed
244 245
        if !self.append_duplicate_entries
            && self.buffers.back().map(|b| b.to_string()) == Some(new_item.to_string())
246 247 248
        {
            return Ok(());
        }
Michael Aaron Murphy's avatar
Michael Aaron Murphy committed
249

250
        let item_str = String::from(new_item.clone());
251
        self.buffers.push_back(new_item);
252 253 254 255 256
        //self.to_max_size();
        while self.buffers.len() > self.max_buffers_size {
            self.buffers.pop_front();
        }

257
        if self.inc_append && self.file_name.is_some() {
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
            if !self.load_duplicates {
                // Do not want duplicates so periodically compact the history file.
                self.compaction_writes += 1;
                // Every 30 writes "compact" the history file by writing just in memory history.  This
                // is to keep the history file clean and at a reasonable size (not much over max
                // history size at it's worst).
                if self.compaction_writes > 29 {
                    if self.share {
                        // Reload history, we may be out of sync.
                        let _ = self.load_history(false);
                        // Commit the duplicated history.
                        if let Some(file_name) = self.file_name.clone() {
                            let _ = self.overwrite_history(file_name);
                        }
                    } else {
                        // Not using shared history so just de-dup the file without messing with
                        // our history.
                        if let Some(file_name) = self.file_name.clone() {
Steven Stanfield's avatar
Steven Stanfield committed
276 277
                            let _ =
                                History::deduplicate_history_file(file_name, self.max_file_size);
278 279 280 281 282 283 284
                        }
                    }
                    self.compaction_writes = 0;
                }
            } else {
                // If allowing duplicates then no need for compaction.
                self.compaction_writes = 1;
285 286 287
            }
            let file_name = self.file_name.clone().unwrap();
            if let Ok(inner_file) = std::fs::OpenOptions::new().append(true).open(&file_name) {
288
                // Leave file size alone, if it is not right trigger a reload later.
Steven Stanfield's avatar
Steven Stanfield committed
289 290
                if self.compaction_writes > 0 {
                    // If 0 we "compacted" and nothing to write.
291 292 293 294 295 296 297
                    let mut file = BufWriter::new(inner_file);
                    let _ = file.write_all(&item_str.as_bytes());
                    let _ = file.write_all(b"\n");
                    // Save the filesize after each append so we do not reload when we do not need to.
                    self.file_size += item_str.len() as u64 + 1;
                }
            }
298
        }
299
        Ok(())
300 301
    }

302 303 304 305 306 307 308 309
    /// Removes duplicate entries in the history
    pub fn remove_duplicates(&mut self, input: &str) {
        self.buffers.retain(|buffer| {
            let command = buffer.lines().concat();
            command != input
        });
    }

310
    fn get_match<I>(&self, vals: I, search_term: &Buffer) -> Option<usize>
311 312
    where
        I: Iterator<Item = usize>,
313
    {
Steven Stanfield's avatar
Steven Stanfield committed
314
        vals.filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
AdminXVII's avatar
AdminXVII committed
315
            .find(|(_i, tested)| tested.starts_with(search_term))
316
            .map(|(i, _)| i)
317 318
    }

319 320
    /// Go through the history and try to find an index (newest to oldest) which starts the same
    /// as the new buffer given to this function as argument.  Starts at curr_position.  Does no wrap.
321 322 323 324 325
    pub fn get_newest_match(
        &self,
        curr_position: Option<usize>,
        new_buff: &Buffer,
    ) -> Option<usize> {
326 327 328 329 330 331 332 333
        let pos = curr_position.unwrap_or_else(|| self.buffers.len());
        if pos > 0 {
            self.get_match((0..pos).rev(), new_buff)
        } else {
            None
        }
    }

334 335
    pub fn get_history_subset(&self, search_term: &Buffer) -> Vec<usize> {
        let mut v: Vec<usize> = Vec::new();
336 337 338 339 340 341 342 343
        let mut ret: Vec<usize> = (0..self.len())
            .filter(|i| {
                if let Some(tested) = self.buffers.get(*i) {
                    let starts = tested.starts_with(search_term);
                    let contains = tested.contains(search_term);
                    if starts {
                        v.push(*i);
                    }
AdminXVII's avatar
AdminXVII committed
344 345 346
                    contains && !starts && tested != search_term
                } else {
                    false
347
                }
348 349
            })
            .collect();
350 351 352 353
        ret.append(&mut v);
        ret
    }

354 355 356
    pub fn search_index(&self, search_term: &Buffer) -> Vec<usize> {
        (0..self.len())
            .filter_map(|i| self.buffers.get(i).map(|t| (i, t)))
Steven Stanfield's avatar
Steven Stanfield committed
357
            .filter(|(_i, tested)| tested.contains(search_term))
358 359
            .map(|(i, _)| i)
            .collect()
360 361
    }

MovingtoMars's avatar
MovingtoMars committed
362
    /// Get the history file name.
363
    #[inline(always)]
MovingtoMars's avatar
MovingtoMars committed
364
    pub fn file_name(&self) -> Option<&str> {
365
        self.file_name.as_ref().map(|s| s.as_str())
366 367
    }

AdminXVII's avatar
AdminXVII committed
368
    fn truncate(&mut self) {
369 370 371 372 373 374
        // Find how many lines we need to move backwards
        // in the file to remove all the old commands.
        if self.buffers.len() >= self.max_file_size {
            let pop_out = self.buffers.len() - self.max_file_size;
            for _ in 0..pop_out {
                self.buffers.pop_front();
375
            }
376 377
        }
    }
378

379
    fn overwrite_history<P: AsRef<Path>>(&mut self, path: P) -> io::Result<String> {
AdminXVII's avatar
AdminXVII committed
380
        self.truncate();
381
        let mut file = BufWriter::new(File::create(&path)?);
382

383 384 385 386 387 388 389 390
        // Write the commands to the history file.
        for command in self.buffers.iter().cloned() {
            let _ = file.write_all(&String::from(command).as_bytes());
            let _ = file.write_all(b"\n");
        }
        Ok("Wrote history to file.".to_string())
    }

391 392 393 394 395 396 397 398
    pub fn commit_to_file_path<P: AsRef<Path>>(&mut self, path: P) -> io::Result<String> {
        if self.inc_append {
            Ok("Nothing to commit.".to_string())
        } else {
            self.overwrite_history(path)
        }
    }

399 400 401
    pub fn commit_to_file(&mut self) {
        if let Some(file_name) = self.file_name.clone() {
            let _ = self.commit_to_file_path(file_name);
Sehny's avatar
Sehny committed
402
        }
403 404 405 406 407 408 409 410
    }
}

impl<'a> IntoIterator for &'a History {
    type Item = &'a Buffer;
    type IntoIter = vec_deque::Iter<'a, Buffer>;

    fn into_iter(self) -> Self::IntoIter {
411
        self.buffers.iter()
412
    }
413 414 415 416 417 418
}

impl Index<usize> for History {
    type Output = Buffer;

    fn index(&self, index: usize) -> &Buffer {
Sehny's avatar
Sehny committed
419
        &self.buffers[index]
420 421 422 423 424
    }
}

impl IndexMut<usize> for History {
    fn index_mut(&mut self, index: usize) -> &mut Buffer {
Sehny's avatar
Sehny committed
425
        &mut self.buffers[index]
426
    }
Sehny's avatar
Sehny committed
427
}