Skip to content

Commit

Permalink
Merge pull request #16 from markbt/new-files
Browse files Browse the repository at this point in the history
Implement new file reader
  • Loading branch information
markbt committed Jan 19, 2020
2 parents 7a44dfe + bfb5743 commit a0a734e
Show file tree
Hide file tree
Showing 9 changed files with 716 additions and 29 deletions.
224 changes: 224 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ clap = { version = "2.32.0", features = ["wrap_help"] }
lazy_static = "1.3.0"
lru-cache = "0.1.2"
memmap = "0.7.0"
notify = "4.0.15"
regex = "1.1.5"
scopeguard = "1.0.0"
smallvec = "1.1.0"
tempfile = "3.1.0"
terminfo = "0.7"
termwiz = "0.6"
unicode-segmentation = "1.2.1"
Expand Down
5 changes: 5 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ impl Buffer {
let data = unsafe { &*self.mmap.get() };
&data[..end]
}

/// Returns the size of the readable portion of the buffer
pub(crate) fn available(&self) -> usize {
self.filled.load(Ordering::SeqCst)
}
}

impl<'buffer> BufferWrite<'buffer> {
Expand Down
176 changes: 176 additions & 0 deletions src/buffer_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//! Buffer Cache.
use anyhow::Error;
use lru_cache::LruCache;
use std::borrow::Cow;
use std::fs::File as StdFile;
use std::io::{Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};

use crate::buffer::Buffer;

pub(crate) struct BufferCache {
path: PathBuf,
file: Option<StdFile>,
cache: LruCache<usize, Buffer>,
block_size: usize,
}

impl BufferCache {
pub(crate) fn new<P: AsRef<Path>>(path: P, block_size: usize, capacity: usize) -> Self {
let path = path.as_ref();
BufferCache {
path: path.to_path_buf(),
file: None,
cache: LruCache::new(capacity),
block_size,
}
}

pub(crate) fn clear(&mut self) {
self.cache.clear();
self.file = None;
}

fn open_file(&mut self) -> Result<(), Error> {
if self.file.is_none() {
self.file = Some(StdFile::open(&self.path)?);
}
Ok(())
}

fn get_buffer<'a>(
&'a mut self,
start: usize,
end: usize,
) -> Result<Option<&'a mut Buffer>, Error> {
let block_index = start / self.block_size;
let block_offset = start % self.block_size;
self.open_file()?;
if let Some(buffer) = self.cache.get_mut(&block_index) {
fill_buffer(
self.file.as_mut().expect("file is open"),
buffer,
start,
block_offset,
end - start,
)?;
} else {
let mut buffer = Buffer::new(self.block_size);
fill_buffer(
self.file.as_mut().expect("file is open"),
&mut buffer,
block_index * self.block_size,
0,
self.block_size,
)?;
self.cache.insert(block_index, buffer);
}
Ok(self.cache.get_mut(&block_index))
}

fn get_data<'a>(&'a mut self, start: usize, end: usize) -> Result<&'a [u8], Error> {
let block_size = self.block_size;
if let Some(buffer) = self.get_buffer(start, end)? {
let data = buffer.read();
let data_start = data.len().min(start % block_size);
let data_end = (data.len()).min((end - 1) % block_size + 1);
Ok(&data[data_start..data_end])
} else {
Ok(&[])
}
}

pub(crate) fn with_slice<T, F>(
&mut self,
start: usize,
end: usize,
mut call: F,
) -> Result<T, Error>
where
F: FnMut(Cow<'_, [u8]>) -> T,
{
let start_block = start / self.block_size;
let end_block = (end - 1) / self.block_size;
if start_block == end_block {
Ok(call(Cow::Borrowed(self.get_data(start, end)?)))
} else {
// The data spans multiple buffers, so we must make a copy to make it contiguous.
// Ensure we fill in any gaps that might occur.
let mut v = Vec::with_capacity(end - start);
let first_end = (start_block + 1) * self.block_size;
let first_slice = self.get_data(start, first_end)?;
v.extend_from_slice(first_slice);
v.resize(first_end - start, 0);
for b in start_block + 1..end_block {
let block_start = b * self.block_size;
let block_end = block_start + self.block_size;
let block_slice = self.get_data(block_start, block_end)?;
v.extend_from_slice(block_slice);
v.resize(block_end - start, 0);
}
let end_start = end_block * self.block_size;
let end_slice = self.get_data(end_start, end)?;
v.extend_from_slice(end_slice);
v.resize(end - start, 0);
Ok(call(Cow::Owned(v)))
}
}
}

fn fill_buffer(
file: &mut StdFile,
buffer: &mut Buffer,
file_offset: usize,
buffer_offset: usize,
len: usize,
) -> Result<(), Error> {
if buffer_offset + len <= buffer.available() {
return Ok(());
}
let mut write = buffer.write();
if file.seek(SeekFrom::Start(file_offset as u64)).is_err() {
// Ignore seek errors, treat them as though the data isn't there.
return Ok(());
}
loop {
match file.read(&mut write) {
Ok(0) => {
// We're at the end of the file. Nothing to do.
break;
}
Ok(len) => {
// Some data has been read.
write.written(len);
break;
}
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(e) => {
return Err(e.into());
}
}
}
Ok(())
}

#[cfg(test)]
mod test {
use super::*;
use anyhow::Error;
use std::io::Write;
use tempfile::NamedTempFile;

#[test]
fn basic() -> Result<(), Error> {
let mut t = NamedTempFile::new()?;
write!(t, "HERE IS SOME DATA")?;
t.flush()?;
let mut c = BufferCache::new(t.path(), 4, 2);
let mut read_range = |start, end| c.with_slice(start, end, |data| data.into_owned());
assert_eq!(read_range(0, 4)?.as_slice(), b"HERE");
assert_eq!(read_range(5, 7)?.as_slice(), b"IS");
assert_eq!(read_range(3, 9)?.as_slice(), b"E IS S");
assert_eq!(read_range(0, 17)?.as_slice(), b"HERE IS SOME DATA");
assert_eq!(read_range(0, 20)?.as_slice(), b"HERE IS SOME DATA\0\0\0");
Ok(())
}
}
11 changes: 11 additions & 0 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,17 @@ pub(crate) fn start(
Some(Event::Loaded(index)) if screens.is_current_index(index) => {
Some(Action::Refresh)
}
Some(Event::Appending(index)) if screens.is_current_index(index) => {
Some(Action::Refresh)
}
Some(Event::Reloading(index)) => {
screens.get(index).map(|screen| screen.flush_line_caches());
if screens.is_current_index(index) {
Some(Action::Refresh)
} else {
None
}
}
Some(Event::SearchFirstMatch(index)) => screens
.get(index)
.and_then(|screen| screen.search_first_match()),
Expand Down
4 changes: 4 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ pub(crate) enum Event {
Input(InputEvent),
/// A file has finished loading.
Loaded(usize),
/// A file has started loading more data.
Appending(usize),
/// A file has started reloading.
Reloading(usize),
/// Render an update to the screen.
Render,
/// Refresh the whole screen.
Expand Down
Loading

0 comments on commit a0a734e

Please sign in to comment.