use crate::{FastaIndex, IndexEntry};
use anyhow::{bail, Result};
use memmap2::Mmap;
use std::fs::File;
#[derive(Debug)]
pub struct IndexedFasta {
index: FastaIndex,
map: Mmap,
buffer: Vec<u8>,
}
impl IndexedFasta {
pub fn new(index: FastaIndex, path: &str) -> Result<Self> {
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
let buffer = Vec::new();
Ok(Self {
index,
map: mmap,
buffer,
})
}
fn validate_interval(
&self,
entry: &IndexEntry,
start: usize,
end: usize,
bounded: bool,
) -> Result<()> {
if start > end {
bail!("Start position must be less than end position");
} else if start == end {
bail!("Start and end positions must not be equal");
} else if start >= entry.length {
bail!("Start position must be less than sequence length");
} else if bounded && end > entry.length {
bail!("End position must be less than sequence length");
}
Ok(())
}
pub fn query(&mut self, name: &str, start: usize, end: usize) -> Result<&[u8]> {
let entry = match self.index.get(name) {
Some(entry) => entry,
None => bail!("No entry found for {}", name),
};
self.validate_interval(entry, start, end, true)?;
self.buffer.clear();
let query_pos = QueryPosition::new(start, end, entry);
let seq_slice = &self.map[query_pos.pos..query_pos.pos + query_pos.buffer_size];
self.buffer.extend_from_slice(seq_slice);
self.buffer.retain(|&c| c != b'\n');
Ok(&self.buffer)
}
pub fn query_buffer(&self, name: &str, start: usize, end: usize) -> Result<&[u8]> {
let entry = match self.index.get(name) {
Some(entry) => entry,
None => bail!("No entry found for {}", name),
};
self.validate_interval(entry, start, end, true)?;
let query_pos = QueryPosition::new(start, end, entry);
let seq_slice = &self.map[query_pos.pos..query_pos.pos + query_pos.buffer_size];
Ok(seq_slice)
}
pub fn query_unbounded(&mut self, name: &str, start: usize, end: usize) -> Result<&[u8]> {
let entry = match self.index.get(name) {
Some(entry) => entry,
None => bail!("No entry found for {}", name),
};
self.validate_interval(entry, start, end, false)?;
let end = if end > entry.length {
entry.length
} else {
end
};
self.buffer.clear();
let query_pos = QueryPosition::new(start, end, entry);
let seq_slice = &self.map[query_pos.pos..query_pos.pos + query_pos.buffer_size];
self.buffer.extend_from_slice(seq_slice);
self.buffer.retain(|&c| c != b'\n');
Ok(&self.buffer)
}
pub fn query_buffer_unbounded(&self, name: &str, start: usize, end: usize) -> Result<&[u8]> {
let entry = match self.index.get(name) {
Some(entry) => entry,
None => bail!("No entry found for {}", name),
};
self.validate_interval(entry, start, end, false)?;
let end = if end > entry.length {
entry.length
} else {
end
};
let query_pos = QueryPosition::new(start, end, entry);
let seq_slice = &self.map[query_pos.pos..query_pos.pos + query_pos.buffer_size];
Ok(seq_slice)
}
}
struct QueryPosition {
pub buffer_size: usize,
pub pos: usize,
}
impl QueryPosition {
pub fn new(start: usize, end: usize, entry: &IndexEntry) -> Self {
let size = end - start;
let row_pos = (start / entry.line_bases) * entry.line_width;
let col_pos = start % entry.line_bases;
let num_lines = (size + col_pos) / entry.line_bases;
let buffer_size = size + num_lines;
let pos = entry.offset + row_pos + col_pos;
Self { buffer_size, pos }
}
}