Thomas пре 1 месец
родитељ
комит
f300248541
3 измењених фајлова са 66 додато и 52 уклоњено
  1. 15 15
      Cargo.lock
  2. 50 16
      src/io/fasta.rs
  3. 1 21
      src/io/vcf.rs

+ 15 - 15
Cargo.lock

@@ -216,7 +216,7 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 [[package]]
 name = "arrow"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-arith",
  "arrow-array",
@@ -236,7 +236,7 @@ dependencies = [
 [[package]]
 name = "arrow-arith"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -249,7 +249,7 @@ dependencies = [
 [[package]]
 name = "arrow-array"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "ahash 0.8.12",
  "arrow-buffer",
@@ -266,7 +266,7 @@ dependencies = [
 [[package]]
 name = "arrow-buffer"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "bytes",
  "half",
@@ -277,7 +277,7 @@ dependencies = [
 [[package]]
 name = "arrow-cast"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -297,7 +297,7 @@ dependencies = [
 [[package]]
 name = "arrow-csv"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-cast",
@@ -311,7 +311,7 @@ dependencies = [
 [[package]]
 name = "arrow-data"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-buffer",
  "arrow-schema",
@@ -323,7 +323,7 @@ dependencies = [
 [[package]]
 name = "arrow-ipc"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -336,7 +336,7 @@ dependencies = [
 [[package]]
 name = "arrow-json"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -360,7 +360,7 @@ dependencies = [
 [[package]]
 name = "arrow-ord"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -372,7 +372,7 @@ dependencies = [
 [[package]]
 name = "arrow-row"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -384,12 +384,12 @@ dependencies = [
 [[package]]
 name = "arrow-schema"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 
 [[package]]
 name = "arrow-select"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "ahash 0.8.12",
  "arrow-array",
@@ -402,7 +402,7 @@ dependencies = [
 [[package]]
 name = "arrow-string"
 version = "59.0.0"
-source = "git+https://github.com/apache/arrow-rs#43d22d82c1571f9442925c07e83d6b54279a1457"
+source = "git+https://github.com/apache/arrow-rs#d628e58b21461ef40d3f6652688710a78dd627b8"
 dependencies = [
  "arrow-array",
  "arrow-buffer",
@@ -2983,7 +2983,7 @@ dependencies = [
 [[package]]
 name = "pandora_lib_smb"
 version = "0.1.0"
-source = "git+https://git.t0m4.fr/Thomas/pandora_lib_smb#54efce4c2146469329225be43212c03825f8b712"
+source = "git+https://git.t0m4.fr/Thomas/pandora_lib_smb#e28b7b3b162c6634d1834a628818a6de124d978e"
 dependencies = [
  "anyhow",
  "blake3",

+ 50 - 16
src/io/fasta.rs

@@ -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`.

+ 1 - 21
src/io/vcf.rs

@@ -151,27 +151,7 @@ pub fn write_vcf<B: Borrow<VcfVariant>>(variants: &[B], path: &str) -> anyhow::R
 ///
 /// Parsed [`VcfVariant`]s whose POS is inside `region`, in file order.
 ///
-/// # Errors
-///
-/// Returns an error if the index or file cannot be read, or if a fetched line
-/// fails to parse as a VCF variant.
-// pub fn fetch_vcf(bgz_path: &str, region: &GenomeRange) -> anyhow::Result<Vec<VcfVariant>> {
-//     let mut variants = Vec::new();
-//     fetch_tabix_lines_with(bgz_path, region, |line| {
-//         let v: VcfVariant = line
-//             .parse()
-//             .with_context(|| format!("failed to parse VCF line: {line}"))?;
-//         if v.position.contig == region.contig && region.range.contains(&v.position.position) {
-//             for split in from_multiallelic(v)? {
-//                 variants.push(split);
-//             }
-//         }
-//         Ok(())
-//     })?;
-//     Ok(variants)
-// }
-
-/// Like [`fetch_vcf`] but reuses a pre-opened [`TabixReader`] instead of
+/// Reuses a pre-opened [`TabixReader`] instead of
 /// reopening the index and BGZF file on every call.
 ///
 /// Use this inside `par_chunks` / `for_each_init` workers where one reader