|
|
@@ -19,7 +19,6 @@ use pandora_lib_smb::{SmbReader, SmbSession};
|
|
|
|
|
|
use crate::{positions::GenomePosition, variant::vcf_variant::ReferenceAlternative};
|
|
|
|
|
|
-
|
|
|
pub struct FaiReader<R> {
|
|
|
inner: IndexedReader<std::io::BufReader<R>>,
|
|
|
}
|
|
|
@@ -28,11 +27,18 @@ impl<R> FaiReader<R>
|
|
|
where
|
|
|
R: std::io::Read + std::io::Seek,
|
|
|
{
|
|
|
- /// Build from an already-open data reader plus pre-parsed `.fai` bytes.
|
|
|
+ /// Build from an already-open data reader plus raw `.fai` bytes (parses internally).
|
|
|
fn from_reader(data: R, fai_bytes: &[u8]) -> anyhow::Result<Self> {
|
|
|
let index = fai::io::Reader::new(fai_bytes)
|
|
|
.read_index()
|
|
|
.context("parse .fai index")?;
|
|
|
+ Self::init(data, index)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Build from an already-open data reader plus an already-parsed index.
|
|
|
+ /// This is the cache-friendly entry point — skips re-parsing when the
|
|
|
+ /// `fai::Index` is already known (e.g. cached on the session).
|
|
|
+ fn init(data: R, index: fai::Index) -> anyhow::Result<Self> {
|
|
|
let inner = Builder::default()
|
|
|
.set_index(index)
|
|
|
.build_from_reader(std::io::BufReader::new(data))
|
|
|
@@ -49,41 +55,69 @@ where
|
|
|
impl FaiReader<File> {
|
|
|
/// Open a local indexed FASTA from disk (replaces `open_indexed_fasta`).
|
|
|
pub fn open(reference: &Path) -> anyhow::Result<Self> {
|
|
|
- let fai_path = reference.with_extension(
|
|
|
- format!("{}.fai", reference.extension().and_then(|e| e.to_str()).unwrap_or("")),
|
|
|
- );
|
|
|
+ let fai_path = reference.with_extension(format!(
|
|
|
+ "{}.fai",
|
|
|
+ reference.extension().and_then(|e| e.to_str()).unwrap_or("")
|
|
|
+ ));
|
|
|
// or just reference.to_string_lossy() + ".fai", matching your existing convention
|
|
|
- let fai_bytes = std::fs::read(&fai_path)
|
|
|
- .with_context(|| format!("read {}", fai_path.display()))?;
|
|
|
- let data = File::open(reference)
|
|
|
- .with_context(|| format!("open {}", reference.display()))?;
|
|
|
+ let fai_bytes =
|
|
|
+ std::fs::read(&fai_path).with_context(|| format!("read {}", fai_path.display()))?;
|
|
|
+ let data =
|
|
|
+ File::open(reference).with_context(|| format!("open {}", reference.display()))?;
|
|
|
Self::from_reader(data, &fai_bytes)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl FaiReader<SmbReader> {
|
|
|
- /// Open an indexed FASTA hosted over SMB.
|
|
|
+ /// Open an indexed FASTA hosted over SMB. The `.fai` index is cached to
|
|
|
+ /// disk (keyed by a hash of its remote path) so repeat calls against the
|
|
|
+ /// same reference skip the network round-trip.
|
|
|
pub async fn open_smb(session: &Arc<SmbSession>, fasta_rel_path: &str) -> anyhow::Result<Self> {
|
|
|
let fai_rel_path = format!("{fasta_rel_path}.fai");
|
|
|
- let fai_bytes = session
|
|
|
- .download_file_bytes(&fai_rel_path)
|
|
|
- .await
|
|
|
- .context("download .fai over SMB")?;
|
|
|
+
|
|
|
+ let cache_dir = session
|
|
|
+ .index_cache_dir()
|
|
|
+ .map(|p| p.to_path_buf())
|
|
|
+ .unwrap_or_else(std::env::temp_dir);
|
|
|
+
|
|
|
+ use std::collections::hash_map::DefaultHasher;
|
|
|
+ use std::hash::{Hash, Hasher};
|
|
|
+ let mut hasher = DefaultHasher::new();
|
|
|
+ fai_rel_path.hash(&mut hasher);
|
|
|
+ let fai_cache_path = cache_dir.join(format!("{:016x}.fai", hasher.finish()));
|
|
|
+
|
|
|
+ let fai_bytes = if fai_cache_path.exists() {
|
|
|
+ std::fs::read(&fai_cache_path).context("read cached .fai")?
|
|
|
+ } else {
|
|
|
+ let data = session
|
|
|
+ .download_file_bytes(&fai_rel_path)
|
|
|
+ .await
|
|
|
+ .context("download .fai over SMB")?;
|
|
|
+ let _ = std::fs::create_dir_all(&cache_dir);
|
|
|
+ std::fs::write(&fai_cache_path, &data).context("write .fai cache")?;
|
|
|
+ data
|
|
|
+ };
|
|
|
+
|
|
|
let data = session
|
|
|
.open_smb_reader(fasta_rel_path)
|
|
|
.await
|
|
|
.context("open SMB reader for FASTA data")?;
|
|
|
+
|
|
|
Self::from_reader(data, &fai_bytes)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
impl<R> std::ops::Deref for FaiReader<R> {
|
|
|
type Target = IndexedReader<std::io::BufReader<R>>;
|
|
|
- fn deref(&self) -> &Self::Target { &self.inner }
|
|
|
+ fn deref(&self) -> &Self::Target {
|
|
|
+ &self.inner
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl<R> std::ops::DerefMut for FaiReader<R> {
|
|
|
- fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner }
|
|
|
+ fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
+ &mut self.inner
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Fetch a fixed-width window of reference sequence centred on `pos0`.
|