string_lines/
lib.rs

1//! Raw persistent database for storing string lines.
2//! 
3//! `string_lines` is on [Crates.io][string_lines] and [GitHub][github].
4//! 
5//!# Example
6//! 
7//!```
8//! use string_lines::StringLines;
9//! let mut lines = StringLines::open(
10//! 	"target/push_pop.example"
11//! ).expect("Unable to open file");
12//! for i in 1..101 {    	
13//! 	let line = format!("line {}",i);	
14//! 	let _ = lines.push(&line).expect("Unable to push line");
15//! }
16//!loop {
17//!	match lines.pop().expect("Unable to pop line") {
18//!		Some(line) => {
19//!			println!("{}",line);
20//!		},
21//!		None => {
22//!			break;
23//!		}
24//!	}
25//!}
26//!```
27//!
28
29extern crate file_lock;
30use std::fs::{OpenOptions,File};
31use std::io;
32use std::io::{Seek,SeekFrom,Write,Read};
33use std::fmt;
34use std::path::Path;
35use std::convert::AsRef;
36use std::os::unix::io::AsRawFd;
37use std::error;
38use std::convert::From;
39use std::result;
40use file_lock::{Lock,AccessMode,LockKind};
41use std::string::FromUtf8Error;
42use std::iter::Extend;
43
44#[derive(Debug)]
45/// Raw persistent database for storing string lines.
46pub struct StringLines {
47	file: File,
48	lock: Lock,	
49}
50
51#[derive(Debug)]
52/// Error enum
53pub enum Error {
54	/// File error
55	FileError(io::Error),
56	/// Locking error
57	LockError(file_lock::Error),
58	/// UTF8 error
59	Utf8Error(FromUtf8Error),
60}
61
62impl fmt::Display for Error {
63	fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
64		match self {
65			&Error::FileError(ref error) => {
66				let _ = try!(write!(f,"File error: {}",error));
67			},
68			&Error::LockError(ref error) => {
69				let _ = try!(write!(f,"Locking error: {:?}",error));
70			},
71			&Error::Utf8Error(ref error) => {
72				let _ = try!(write!(f,"UTF8 error: {:?}",error));
73			},
74		}
75		return Ok(());
76	}
77}
78
79impl error::Error for Error {
80	fn description(&self) -> &str {
81		match self {
82			&Error::FileError(..) => {
83				"File error"
84			},
85			&Error::LockError(..) => {
86				"Locking error"
87			},
88			&Error::Utf8Error(..) => {
89				"UTF8 error"
90			},
91		}
92	}
93}
94
95impl From<io::Error> for Error {
96	fn from(error: io::Error) -> Self {
97		return Error::FileError(error);
98	}
99}
100
101impl From<file_lock::Error> for Error {
102	fn from(error: file_lock::Error) -> Self {
103		return Error::LockError(error);
104	}
105}
106
107impl From<FromUtf8Error> for Error {
108	fn from(error: FromUtf8Error) -> Self {
109		return Error::Utf8Error(error);
110	}
111}
112
113pub type Result<T> = result::Result<T,Error>;
114
115impl StringLines {
116	/// Attempts to open a file in read-write mode.
117	pub fn open<P: AsRef<Path>>(path: P) -> Result<StringLines> {
118		let mut options = OpenOptions::new();
119		options.read(true);
120		options.write(true);
121		options.create(true);
122		let file = try!(options.open(path));
123		let lock = Lock::new(file.as_raw_fd());
124		Ok(StringLines {
125			file: file,			
126			lock: lock,			
127		})
128	}
129
130	/// Appends an element to the back of a collection.
131	pub fn push(&mut self,s:&str) -> Result<()> {
132		let s = format!("{}\n",s);
133		let _ = try!(self.lock.lock(LockKind::Blocking, AccessMode::Write));
134		let _ = try!(self.file.seek(SeekFrom::End(0)));
135		let _ = try!(self.file.write(s.as_bytes()));
136		let _ = try!(self.lock.unlock());
137		return Ok(());
138	}
139
140	fn pop_inner(&mut self,mut offset:i64,mut data:Vec<u8>) -> Result<Option<String>> {
141		if offset <= 0 {
142			offset = 0;
143		}
144		let _ = try!(self.file.set_len(offset as u64));
145		let _ = try!(self.lock.unlock());		
146		if data.len() > 0 {			
147			data.reverse();
148			let result = try!(String::from_utf8(data));
149			return Ok(Some(result));
150		} else {
151			return Ok(None);
152		}
153	}
154
155	/// Removes the last element from a collection and returns it, or None if it is empty.
156	pub fn pop(&mut self) -> Result<Option<String>> {
157		let _ = try!(self.lock.lock(LockKind::Blocking, AccessMode::Write));
158		let len = try!(self.file.metadata()).len() as i64;
159		let mut offset: i64 = len - 1024;		
160		let mut data: Vec<u8> = Vec::with_capacity(1024);
161		if offset < 0 {
162			offset = 0;
163		}
164		loop {			
165			let _ = try!(self.file.seek(SeekFrom::Start(offset as u64)));
166			let mut buf = [0; 1024];
167			match self.file.read(&mut buf) {
168				Ok(0) => {
169					break;
170				}
171				Ok(readed) => {
172					let buf = &buf[..readed];					
173					let mut lines: Vec<&[u8]> = buf.split(|c| { *c == 0x0A}).collect();					
174					let lines_len = lines.len() as i32;
175					if lines_len >= 2 {
176						let mut char_offset: i64 = 0;
177						for _ in 0..lines_len - 1 {							
178							if let Some(last_line) = lines.pop() {
179								char_offset = char_offset + 1;
180								if last_line.len() >= 1 {
181									data.extend(last_line.iter().rev());	
182									return self.pop_inner(
183										len - data.len() as i64 - char_offset,
184										data
185									);
186								}
187							}
188						}
189					}
190					offset = offset - buf.len() as i64;
191					data.extend(buf.iter().rev());	
192				},
193				Err(error) => {
194					let _ = try!(self.lock.unlock());
195					return Err(Error::from(error));
196				},
197			}
198			if offset < 0 {
199				break;
200			}		
201		}		
202		return self.pop_inner(offset,data);			
203	}
204
205	/// Returns the number of elements in the collection.
206	pub fn clear(&mut self) -> Result<()> {
207		let _ = try!(self.lock.lock(LockKind::Blocking, AccessMode::Write));
208		let _ = try!(self.file.set_len(0));
209		let _ = try!(self.lock.unlock());		
210		return Ok(());
211	}
212}
213
214#[cfg(test)]
215mod tests {
216	use super::*;
217	use std::fs::remove_file;
218
219    #[test]
220    fn test_push_pop() {
221    	let path = "target/test_push_pop.test";
222    	let _ = remove_file(&path);
223    	let mut lines = StringLines::open(path).expect("Unable to open file");
224    	let mut items = vec![];
225    	for i in 1..101 {    	
226	    	let line = format!("line {}",i);	
227	    	let _ = lines.push(&line).expect("Unable to push line");
228	    	items.push(line);
229    	}
230    	loop {
231    		match items.pop() {
232    			Some(item_line) => {
233    				let line = lines.pop().expect("Unable to pop line");    				
234		    		assert_eq!(Some(item_line), line);
235    			},
236    			None => {
237    				break;
238    			},
239    		}
240    	}
241    	let line = lines.pop().expect("Unable to pop line");
242    	assert_eq!(line, None);
243    	let line = lines.pop().expect("Unable to pop line");
244    	assert_eq!(line, None);
245    	let line = lines.pop().expect("Unable to pop line");
246    	assert_eq!(line, None);
247    }
248
249    #[test]
250    fn test_len() {
251    	let path = "target/test_len.test";
252    	let _ = remove_file(&path);
253    	let _ = remove_file(&path);
254    	let mut lines = StringLines::open(path).expect("Unable to open file");
255    	let mut items = vec![];
256    	for i in 1..101 {    	
257	    	let line = format!("line {}",i);	
258	    	let _ = lines.push(&line).expect("Unable to push line");
259	    	items.push(line);
260    	}
261    	lines.clear().expect("Unable to clear collection");
262    	let line = lines.pop().expect("Unable to pop line");
263    	assert_eq!(line, None);
264    	let line = lines.pop().expect("Unable to pop line");
265    	assert_eq!(line, None);
266    	let line = lines.pop().expect("Unable to pop line");
267    	assert_eq!(line, None);
268    }
269}