1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use super::*;
use std::collections::VecDeque;
use std::io::{BufReader, BufRead, Error, ErrorKind};
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::ops::Index;
use std::ops::IndexMut;
const DEFAULT_MAX_SIZE: usize = 1000;
/// Structure encapsulating command history
pub struct History {
// TODO: this should eventually be private
/// Vector of buffers to store history in
pub buffers: VecDeque<Buffer>,
// TODO: Do we need this here? Ion can take care of this.
// pub previous_status: i32,
/// Store a filename to save history into; if None don't save history
file_name: Option<String>,
/// Maximal number of buffers stored in the memory
max_size: usize,
/// Maximal number of lines stored in the file
max_file_size: usize,
}
impl History {
/// Create new History structure.
pub fn new() -> History {
History {
buffers: VecDeque::with_capacity(DEFAULT_MAX_SIZE),
file_name: None,
max_size: DEFAULT_MAX_SIZE,
max_file_size: DEFAULT_MAX_SIZE,
}
}
/// Number of items in history.
pub fn len(&self) -> usize {
self.buffers.len()
}
/// Add a command to the history buffer and remove the oldest commands when the max history
/// size has been met. If writing to the disk is enabled, this function will be used for
/// logging history to the designated history file.
pub fn push(&mut self, new_item: Buffer) -> io::Result<()> {
let mut ret = Ok(());
self.file_name.as_ref().map(|name| {
ret = self.write_to_disk(&new_item, name);
});
// buffers[0] is the oldest entry
// the new entry goes to the end
self.buffers.push_back(new_item);
while self.buffers.len() > self.max_size {
self.buffers.pop_front();
}
ret
}
/// Get the history file name.
pub fn file_name(&self) -> Option<&str> {
match self.file_name {
Some(ref s) => Some(&s[..]),
None => None,
}
}
/// Set history file name. At the same time enable history.
pub fn set_file_name(&mut self, name: Option<String>) {
self.file_name = name;
}
/// Set maximal number of buffers stored in memory
pub fn set_max_size(&mut self, size: usize) {
self.max_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;
}
/// Load history from given file name
pub fn load_history(&mut self) -> io::Result<()> {
let file_name = match self.file_name.clone() {
Some(name) => name,
None => return Err(Error::new(ErrorKind::Other, "Liner: file name not specified")),
};
let file = try!(OpenOptions::new().read(true).open(file_name));
let reader = BufReader::new(file);
for line in reader.lines() {
match line {
Ok(line) => self.buffers.push_back(Buffer::from(line)),
Err(_) => break,
}
}
Ok(())
}
/// Perform write operation. If the history file does not exist, it will be created.
/// This function is not part of the public interface.
/// XXX: include more information in the file (like fish does)
fn write_to_disk(&self, new_item: &Buffer, file_name: &String) -> io::Result<()> {
let ret = match OpenOptions::new().read(true).write(true).create(true).open(file_name) {
Ok(mut file) => {
// Determine the number of commands stored and the file length.
let (file_length, commands_stored) = {
let mut commands_stored = 0;
let mut file_length = 0;
let file = File::open(file_name).unwrap();
for byte in file.bytes() {
if byte.unwrap_or(b' ') == b'\n' {
commands_stored += 1;
}
file_length += 1;
}
(file_length, commands_stored)
};
// If the max history file size has been reached, truncate the file so that only
// N amount of commands are listed. To truncate the file, the seek point will be
// discovered by counting the number of bytes until N newlines have been found and
// then the file will be seeked to that point, copying all data after and rewriting
// the file with the first N lines removed.
if commands_stored >= self.max_file_size {
let seek_point = {
let commands_to_delete = commands_stored - self.max_file_size + 1;
let mut matched = 0;
let mut bytes = 0;
let file = File::open(file_name).unwrap();
for byte in file.bytes() {
if byte.unwrap_or(b' ') == b'\n' {
matched += 1;
}
bytes += 1;
if matched == commands_to_delete {
break;
}
}
bytes as u64
};
try!(file.seek(SeekFrom::Start(seek_point)));
let mut buffer: Vec<u8> = Vec::with_capacity(file_length - seek_point as usize);
try!(file.read_to_end(&mut buffer));
try!(file.set_len(0));
try!(io::copy(&mut buffer.as_slice(), &mut file));
}
// Seek to end for appending
try!(file.seek(SeekFrom::End(0)));
// Write the command to the history file.
try!(file.write_all(String::from(new_item.clone()).as_bytes()));
try!(file.write(b"\n"));
Ok(())
}
Err(message) => Err(message),
};
ret
}
}
impl Index<usize> for History {
type Output = Buffer;
fn index(&self, index: usize) -> &Buffer {
&self.buffers[index]
}
}
impl IndexMut<usize> for History {
fn index_mut(&mut self, index: usize) -> &mut Buffer {
&mut self.buffers[index]
}
}