1use std::sync::Arc;
26
27use bytes::Bytes;
28use parking_lot::Mutex;
29
30use crate::bam::header::{HeaderLine, SamHeader};
31use crate::bam::record::{decode_block, BamRecord, EntryFilter};
32use crate::bam::EntriesRequest;
33use crate::error::{Error, Result};
34use crate::genomic::{ChrMap, Locs};
35use crate::parallel::Executor;
36use crate::progress::ProgressTracker;
37use crate::source::ByteSource;
38
39use super::compression::CompressionHeader;
40use super::container::{
41 Block, BlockContentType, ContainerHeader, FileDefinition, EOF_CONTAINER, FILE_DEFINITION_SIZE,
42};
43use super::crai::{CramIndex, IndexEntry};
44use super::record::{decode_slice, ReferenceBases, References};
45use super::reference::{resolve, Reference, ReferenceSource};
46use super::slice::Slice;
47
48const CONTAINER_CACHE: usize = 8;
51
52const SLICE_CACHE_BYTES: usize = 64 << 20;
55
56#[derive(Debug)]
57struct Inner {
58 source: Arc<dyn ByteSource>,
59 executor: Executor,
60 index: Option<Arc<CramIndex>>,
62 built: Mutex<Option<Arc<CramIndex>>>,
66 reference: Option<ReferenceSource>,
67 caches: Mutex<Caches>,
68 slice_ready: parking_lot::Condvar,
71}
72
73#[derive(Debug, Default)]
75struct Caches {
76 containers: Vec<(u64, Arc<ContainerHeader>, Arc<CompressionHeader>)>,
78 slices: Vec<(u64, u64, Bytes)>,
80 slice_bytes: usize,
81 in_flight: Vec<(u64, u64)>,
89}
90
91pub struct CramReader {
92 inner: Option<Inner>,
93 path: String,
94 index_path: String,
95 header: SamHeader,
96 chr_map: ChrMap,
97 chr_names: Arc<Vec<String>>,
99 read_groups: Vec<String>,
102 version: (u8, u8),
103 index_error: String,
104 indexed: bool,
105 reference: Reference,
106 reference_error: String,
107}
108
109impl std::fmt::Debug for CramReader {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("CramReader")
112 .field("path", &self.path)
113 .field("version", &self.version)
114 .field("references", &self.chr_map.len())
115 .field("reference", &self.reference.path())
116 .field("closed", &self.is_closed())
117 .finish()
118 }
119}
120
121impl CramReader {
122 pub fn open(
123 path: &str,
124 index_path: Option<&str>,
125 reference: Option<&str>,
126 parallel: i64,
127 block_size: Option<u64>,
128 max_blocks: Option<usize>,
129 ) -> Result<Self> {
130 let source = crate::source::open(path, block_size, max_blocks)?;
131 Self::from_source(
132 source, path, index_path, reference, parallel, block_size, max_blocks,
133 )
134 }
135
136 pub(crate) fn from_source(
137 source: Arc<dyn ByteSource>,
138 path: &str,
139 index_path: Option<&str>,
140 reference: Option<&str>,
141 parallel: i64,
142 block_size: Option<u64>,
143 max_blocks: Option<usize>,
144 ) -> Result<Self> {
145 let definition = FileDefinition::parse(&source.read_at(0, FILE_DEFINITION_SIZE)?, path)?;
146 let (header, chr_map, read_groups) = read_header(source.as_ref(), path)?;
147
148 let mut names = vec![String::new(); chr_map.iter().map(|e| e.index + 1).max().unwrap_or(0)];
149 for entry in chr_map.iter() {
150 names[entry.index] = entry.id.clone();
151 }
152
153 let index_path_given = index_path;
156 let index_path = index_path
157 .map(str::to_string)
158 .unwrap_or_else(|| format!("{path}.crai"));
159 let named = index_path_given.is_some();
163 let (index, index_error) =
164 if !crate::source::is_url(&index_path) && !std::path::Path::new(&index_path).exists() {
165 (
166 None,
167 if named {
168 format!("{index_path} is not there")
169 } else {
170 String::new()
171 },
172 )
173 } else {
174 match crate::source::open(&index_path, block_size, max_blocks)
175 .and_then(|s| s.read_to_end(0))
176 .and_then(|data| CramIndex::parse(&data, &index_path))
177 {
178 Ok(index) => (Some(Arc::new(index)), String::new()),
179 Err(e) => (None, e.to_string()),
180 }
181 };
182
183 let truncated = match source.len() {
190 Ok(length) if length >= EOF_CONTAINER.len() as u64 => {
191 let at = length - EOF_CONTAINER.len() as u64;
192 !matches!(source.read_at(at, EOF_CONTAINER.len()), Ok(tail) if tail[..] == EOF_CONTAINER[..])
193 }
194 _ => true,
195 };
196
197 let slots = if parallel <= 0 {
203 std::thread::available_parallelism().map_or(4, |n| n.get())
204 } else {
205 parallel as usize
206 };
207 let sequences = sequence_details(&header);
208 let mut resolved = resolve(reference, &sequences);
209 let (mut reference_source, mut reference_error) = match &resolved {
210 Reference::FromCache(root) => (
214 Some(ReferenceSource::open_cache(root, &sequences).with_slots(slots)),
215 String::new(),
216 ),
217 Reference::Given(path) | Reference::FromHeader(path) => {
218 match ReferenceSource::open(path) {
219 Ok(source) => (Some(source.with_slots(slots)), String::new()),
220 Err(e) => (None, e.to_string()),
221 }
222 }
223 Reference::None(why) => (None, why.clone()),
224 };
225
226 if let Some(source) = &reference_source {
233 if let Some(complaint) = reference_complaint(source, &chr_map, &sequences) {
234 reference_source = None;
235 resolved = Reference::None(complaint.clone());
239 reference_error = complaint;
240 }
241 }
242
243 let indexed = index.is_some() || !crate::source::is_url(path);
248 let index_error = if truncated && index_error.is_empty() {
254 format!(
255 "{path} has no EOF container, so it was cut short; anything past the cut is \
256 missing and a locus there will find nothing"
257 )
258 } else if index.is_none() && !index_error.is_empty() && !crate::source::is_url(path) {
259 format!("{index_error} — reading it from the file's containers instead")
260 } else if indexed || !index_error.is_empty() {
261 index_error
262 } else {
263 format!(
264 "{index_path} is not there, and this file is remote, so an index cannot be \
265 built by walking its containers"
266 )
267 };
268
269 Ok(Self {
270 inner: Some(Inner {
271 source,
272 executor: Executor::new(parallel)?,
273 index,
274 built: Mutex::new(None),
275 reference: reference_source,
276 caches: Mutex::new(Caches::default()),
277 slice_ready: parking_lot::Condvar::new(),
278 }),
279 path: path.to_string(),
280 index_path,
281 header,
282 chr_map,
283 chr_names: Arc::new(names),
284 read_groups,
285 version: (definition.major, definition.minor),
286 index_error,
287 indexed,
288 reference: resolved,
289 reference_error,
290 })
291 }
292
293 pub fn header(&self) -> &SamHeader {
294 &self.header
295 }
296 pub fn chr_sizes(&self) -> &ChrMap {
297 &self.chr_map
298 }
299 pub fn index_error(&self) -> &str {
300 &self.index_error
301 }
302 pub fn is_indexed(&self) -> bool {
303 self.indexed
304 }
305 pub fn is_closed(&self) -> bool {
306 self.inner.is_none()
307 }
308 pub fn path(&self) -> &str {
309 &self.path
310 }
311 pub fn parallel(&self) -> usize {
312 self.inner.as_ref().map_or(0, |i| i.executor.parallel())
313 }
314 pub fn version(&self) -> (u8, u8) {
316 self.version
317 }
318 pub fn reference_path(&self) -> Option<&str> {
320 self.reference.path()
321 }
322 pub fn reference_error(&self) -> &str {
324 &self.reference_error
325 }
326 pub fn close(&mut self) {
327 if let Some(inner) = self.inner.take() {
328 inner.source.close();
329 }
330 }
331
332 fn inner(&self) -> Result<&Inner> {
333 self.inner.as_ref().ok_or_else(|| Error::Closed {
334 path: self.path.clone(),
335 })
336 }
337
338 fn index(&self, inner: &Inner) -> Result<Arc<CramIndex>> {
340 if let Some(index) = &inner.index {
341 return Ok(index.clone());
342 }
343 if !self.index_error.is_empty() && crate::source::is_url(&self.path) {
347 return Err(Error::invalid(format!(
348 "cram index {} could not be read: {}",
349 self.index_path, self.index_error
350 )));
351 }
352 let mut built = inner.built.lock();
353 if let Some(index) = built.as_ref() {
354 return Ok(index.clone());
355 }
356 let index = Arc::new(CramIndex::build(inner.source.as_ref())?);
357 *built = Some(index.clone());
358 Ok(index)
359 }
360
361 pub fn read_entries(&self, req: &EntriesRequest) -> Result<Vec<Vec<BamRecord>>> {
363 let inner = self.inner()?;
364 let resolved = self.resolve(&req.locs)?;
365 let coverage = resolved.iter().map(|(_, s, e)| (e - s).max(0) as u64).sum();
366 let tracker = ProgressTracker::with_callback(coverage, req.progress.clone());
367 let index = self.index(inner)?;
368
369 let workers = inner.executor.parallel().min(resolved.len().max(1)).max(1);
373 let per_worker = resolved.len().div_ceil(workers).max(1);
374 let batches: Vec<(usize, usize)> = (0..workers)
375 .map(|w| (w * per_worker, ((w + 1) * per_worker).min(resolved.len())))
376 .filter(|(from, to)| from < to)
377 .collect();
378
379 let lists = inner.executor.map_batches(&batches, |_, (from, to)| {
380 let mut out = Vec::with_capacity(to - from);
381 for locus in &resolved[*from..*to] {
382 out.push(self.read_locus(inner, &index, *locus, req)?);
383 tracker.add((locus.2 - locus.1).max(0) as u64);
384 }
385 Ok(out)
386 })?;
387 tracker.done_report();
388 Ok(lists.into_iter().flatten().collect())
389 }
390
391 pub fn read_all_entries(&self, req: &EntriesRequest) -> Result<Vec<BamRecord>> {
394 let locs = Locs::whole_chromosomes(&self.chr_map, &req.locs.chr_ids)?;
395 let whole = EntriesRequest {
396 locs,
397 ..req.clone()
398 };
399 Ok(self.read_entries(&whole)?.into_iter().flatten().collect())
400 }
401
402 pub fn iter_entries(&self, req: &EntriesRequest) -> Result<LocusEntries<'_>> {
404 LocusEntries::plan(self, req)
405 }
406
407 pub fn iter_all_entries(&self, req: &EntriesRequest, window: i64) -> Result<WindowEntries<'_>> {
409 WindowEntries::plan(self, req, window)
410 }
411
412 fn resolve(&self, locs: &Locs) -> Result<Vec<(usize, i64, i64)>> {
413 (0..locs.len())
414 .map(|i| {
415 let entry = self.chr_map.resolve(&locs.chr_ids[i])?;
416 Ok((entry.index, locs.starts[i], locs.ends[i]))
417 })
418 .collect()
419 }
420
421 fn read_locus(
423 &self,
424 inner: &Inner,
425 index: &CramIndex,
426 locus: (usize, i64, i64),
427 req: &EntriesRequest,
428 ) -> Result<Vec<BamRecord>> {
429 let (chr, start, end) = locus;
430 let filter = EntryFilter {
431 chr_index: Some(chr as i32),
432 start,
433 end: Some(end),
434 standard_flags: req.filter.enabled,
435 };
436 let mut out = Vec::new();
437 for entry in index.slices(chr as i32, start, end) {
438 let records = self.slice_records(inner, &entry)?;
439 out.extend(decode_block(
440 &records,
441 req.parse_tags,
442 &filter,
443 &self.chr_names,
444 &self.path,
445 )?);
446 }
447 Ok(out)
448 }
449
450 fn slice_records(&self, inner: &Inner, entry: &IndexEntry) -> Result<Bytes> {
452 let key = (entry.container_offset, entry.landmark);
453 {
454 let mut caches = inner.caches.lock();
455 loop {
456 if let Some(position) = caches.slices.iter().position(|(c, l, _)| (*c, *l) == key) {
457 let hit = caches.slices.remove(position);
460 let records = hit.2.clone();
461 caches.slices.push(hit);
462 return Ok(records);
463 }
464 if !caches.in_flight.contains(&key) {
465 caches.in_flight.push(key);
466 break;
467 }
468 inner.slice_ready.wait(&mut caches);
472 }
473 }
474 let outcome = self.decode_one_slice(inner, entry);
477 let mut caches = inner.caches.lock();
478 caches.in_flight.retain(|held| *held != key);
479 if let Ok(records) = &outcome {
480 if !caches.slices.iter().any(|(c, l, _)| (*c, *l) == key) {
483 caches.slice_bytes += records.len();
484 caches.slices.push((key.0, key.1, records.clone()));
485 while caches.slice_bytes > SLICE_CACHE_BYTES && caches.slices.len() > 1 {
486 let (_, _, dropped) = caches.slices.remove(0);
487 caches.slice_bytes -= dropped.len();
488 }
489 }
490 }
491 drop(caches);
492 inner.slice_ready.notify_all();
493 outcome
494 }
495
496 fn decode_one_slice(&self, inner: &Inner, entry: &IndexEntry) -> Result<Bytes> {
498 let (container, compression) = self.container(inner, entry.container_offset)?;
499 let offset = container.blocks_offset() + entry.landmark;
500 let slice = Slice::read(inner.source.as_ref(), offset, entry.size)?;
501
502 let window = self.reference_window(inner, &slice)?;
506 let references = match (&window, &inner.reference) {
507 (Some((bases, start)), _) => References::Fixed(ReferenceBases {
508 bases,
509 start: start + 1,
511 }),
512 (None, Some(source)) if slice.header.is_multi_ref() => References::ByRefId {
513 source,
514 names: &self.chr_names,
515 },
516 _ => References::None,
517 };
518 decode_slice(
519 &slice,
520 &compression,
521 references,
522 &self.read_groups,
523 &self.path,
524 )
525 }
526
527 fn container(
529 &self,
530 inner: &Inner,
531 offset: u64,
532 ) -> Result<(Arc<ContainerHeader>, Arc<CompressionHeader>)> {
533 {
534 let mut caches = inner.caches.lock();
535 if let Some(position) = caches.containers.iter().position(|(at, ..)| *at == offset) {
536 let hit = caches.containers.remove(position);
537 let out = (hit.1.clone(), hit.2.clone());
538 caches.containers.push(hit);
539 return Ok(out);
540 }
541 }
542
543 let header = ContainerHeader::read(inner.source.as_ref(), offset)?;
544 let first = header
546 .landmarks
547 .first()
548 .copied()
549 .map(|landmark| landmark.max(0) as usize)
550 .unwrap_or(header.length.max(0) as usize);
551 let data = inner
552 .source
553 .read_exact_at(header.blocks_offset(), first.max(1))?;
554 let block = Block::parse(&data, header.blocks_offset(), &self.path)?;
555 if block.content_type != BlockContentType::CompressionHeader {
556 return Err(Error::corrupt(
557 &self.path,
558 header.blocks_offset(),
559 format!(
560 "the first block of a container is {:?}, not its compression header",
561 block.content_type
562 ),
563 ));
564 }
565 let compression = Arc::new(CompressionHeader::parse(&block.data, &self.path)?);
566 let header = Arc::new(header);
567
568 let mut caches = inner.caches.lock();
569 caches
570 .containers
571 .push((offset, header.clone(), compression.clone()));
572 while caches.containers.len() > CONTAINER_CACHE {
573 caches.containers.remove(0);
574 }
575 Ok((header, compression))
576 }
577
578 fn reference_window(
584 &self,
585 inner: &Inner,
586 slice: &Slice,
587 ) -> Result<Option<(Arc<Vec<u8>>, i64)>> {
588 if let Some(embedded) = &slice.embedded_reference {
590 let start = slice.header.range().map(|(from, _)| from).unwrap_or(0);
591 return Ok(Some((Arc::new(embedded.to_vec()), start)));
592 }
593 let Some(reference) = &inner.reference else {
594 return Ok(None);
595 };
596 let Some((start, end)) = slice.header.range() else {
597 return Ok(None);
598 };
599 let Some(name) = self.chr_names.get(slice.header.ref_id.max(0) as usize) else {
600 return Ok(None);
601 };
602 if !reference.has(name) {
603 return Ok(None);
604 }
605 Ok(Some(reference.window(name, start, end)?))
606 }
607}
608
609fn read_header(source: &dyn ByteSource, path: &str) -> Result<(SamHeader, ChrMap, Vec<String>)> {
615 let container = ContainerHeader::read(source, FILE_DEFINITION_SIZE as u64)?;
616 let data = source.read_exact_at(container.blocks_offset(), container.length.max(0) as usize)?;
617 let block = Block::parse(&data, container.blocks_offset(), path)?;
618 if block.content_type != BlockContentType::FileHeader {
619 return Err(Error::corrupt(
620 path,
621 container.blocks_offset(),
622 format!(
623 "the first container holds a {:?} block where its header should be",
624 block.content_type
625 ),
626 ));
627 }
628 if block.data.len() < 4 {
630 return Err(Error::corrupt(
631 path,
632 container.blocks_offset(),
633 "a header block too short to hold its own length",
634 ));
635 }
636 let length =
637 i32::from_le_bytes(block.data[..4].try_into().expect("four bytes")).max(0) as usize;
638 let text = &block.data[4..(4 + length).min(block.data.len())];
639 let header = SamHeader::parse(&String::from_utf8_lossy(text));
640
641 let mut entries = Vec::new();
642 let mut read_groups = Vec::new();
643 for line in &header.lines {
644 match line.kind.as_str() {
645 "SQ" => {
646 let name = field(line, "SN");
652 let size = field(line, "LN").and_then(|v| v.parse::<i64>().ok());
653 match (name, size) {
654 (Some(name), Some(size)) => entries.push((name.to_string(), size)),
655 (name, _) => {
656 return Err(Error::format(
657 path,
658 format!(
659 "an @SQ line for {} has no usable LN, and reference ids are \
660 counted over these lines, so every later one would shift",
661 name.unwrap_or("an unnamed sequence")
662 ),
663 ))
664 }
665 }
666 }
667 "RG" => {
668 if let Some(id) = field(line, "ID") {
669 read_groups.push(id.to_string());
670 }
671 }
672 _ => {}
673 }
674 }
675 let chr_map = ChrMap::from_entries(entries);
676 Ok((header, chr_map, read_groups))
677}
678
679fn reference_complaint(
694 source: &ReferenceSource,
695 chr_map: &ChrMap,
696 sequences: &[(String, Option<String>, Option<String>)],
697) -> Option<String> {
698 if sequences.is_empty() {
699 return None;
700 }
701 let mut found = 0usize;
702 for (name, ..) in sequences {
703 let Some(length) = source.length(name) else {
704 continue;
705 };
706 found += 1;
707 let declared = chr_map.get(name).map(|entry| entry.size);
708 if let Some(declared) = declared {
709 if declared != length {
710 return Some(format!(
711 "this reference has {name} at {length} bases where the file's header says {declared}, so it is not the assembly these reads were aligned to"
712 ));
713 }
714 }
715 }
716 if found == 0 {
717 let named = sequences
718 .iter()
719 .take(3)
720 .map(|(name, ..)| name.as_str())
721 .collect::<Vec<_>>()
722 .join(", ");
723 return Some(format!(
724 "this reference holds none of the {} sequences this file names ({named}...), so every sequence would read as N",
725 sequences.len()
726 ));
727 }
728 None
729}
730
731fn sequence_details(header: &SamHeader) -> Vec<(String, Option<String>, Option<String>)> {
732 header
733 .lines
734 .iter()
735 .filter(|line| line.kind == "SQ")
736 .filter_map(|line| {
737 Some((
738 field(line, "SN")?.to_string(),
739 field(line, "UR").map(str::to_string),
740 field(line, "M5").map(str::to_string),
741 ))
742 })
743 .collect()
744}
745
746fn field<'a>(line: &'a HeaderLine, tag: &str) -> Option<&'a str> {
747 line.fields
748 .iter()
749 .find(|f| f.tag == tag)
750 .map(|f| f.value.as_str())
751}
752
753struct WalkPlan {
755 loci: Vec<(usize, i64, i64)>,
756 order: Vec<usize>,
757 min_starts: Vec<Option<i64>>,
760 request: EntriesRequest,
761 coverage: u64,
762}
763
764pub struct LocusWalk {
766 plan: Arc<WalkPlan>,
767 next: usize,
768 tracker: Arc<ProgressTracker>,
769}
770
771impl std::fmt::Debug for LocusWalk {
772 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
773 f.debug_struct("LocusWalk")
774 .field("loci", &self.plan.loci.len())
775 .field("next", &self.next)
776 .finish()
777 }
778}
779
780impl LocusWalk {
781 pub fn plan(reader: &CramReader, req: &EntriesRequest) -> Result<Self> {
782 Self::plan_with(reader, req, false)
783 }
784
785 fn plan_with(
786 reader: &CramReader,
787 req: &EntriesRequest,
788 from_locus_start: bool,
789 ) -> Result<Self> {
790 let inner = reader.inner()?;
791 reader.index(inner)?;
794 let resolved = reader.resolve(&req.locs)?;
795 let mut order: Vec<usize> = (0..resolved.len()).collect();
796 if req.sort_locations {
797 order.sort_by_key(|i| resolved[*i]);
798 }
799 let loci: Vec<(usize, i64, i64)> = order.iter().map(|i| resolved[*i]).collect();
800 let coverage = loci.iter().map(|(_, s, e)| (e - s).max(0) as u64).sum();
801 let min_starts = if from_locus_start {
802 loci.iter().map(|(_, start, _)| Some(*start)).collect()
803 } else {
804 vec![None; loci.len()]
805 };
806 Ok(Self {
807 tracker: Arc::new(ProgressTracker::with_callback(
808 coverage,
809 req.progress.clone(),
810 )),
811 plan: Arc::new(WalkPlan {
812 min_starts,
813 loci,
814 order,
815 request: req.clone(),
816 coverage,
817 }),
818 next: 0,
819 })
820 }
821
822 pub fn restarted(&self) -> Self {
824 Self {
825 plan: self.plan.clone(),
826 next: 0,
827 tracker: Arc::new(ProgressTracker::with_callback(
828 self.plan.coverage,
829 self.plan.request.progress.clone(),
830 )),
831 }
832 }
833
834 pub fn plan_windows(reader: &CramReader, req: &EntriesRequest, span: i64) -> Result<Self> {
835 if span < 1 {
836 return Err(Error::invalid(format!(
837 "span must be positive (got {span})"
838 )));
839 }
840 let locs = crate::bam::window_locs(&reader.chr_map, &req.locs.chr_ids, span)?;
841 let windowed = EntriesRequest {
842 locs,
843 sort_locations: false,
844 ..req.clone()
845 };
846 Self::plan_with(reader, &windowed, true)
847 }
848
849 pub fn len(&self) -> usize {
850 self.plan.loci.len()
851 }
852 pub fn is_empty(&self) -> bool {
853 self.plan.loci.is_empty()
854 }
855 pub fn order(&self) -> &[usize] {
856 &self.plan.order
857 }
858
859 pub fn next_window(&mut self, reader: &CramReader) -> Option<Result<Vec<BamRecord>>> {
860 if self.next >= self.plan.loci.len() {
861 self.tracker.done_report();
862 return None;
863 }
864 let index = self.next;
865 let locus = self.plan.loci[index];
866 let outcome = reader.inner().and_then(|inner| {
867 let cram_index = reader.index(inner)?;
868 reader.read_locus(inner, &cram_index, locus, &self.plan.request)
869 });
870 match outcome {
871 Err(e) => Some(Err(e)),
875 Ok(mut records) => {
876 self.next += 1;
877 if let Some(min_start) = self.plan.min_starts[index] {
878 records.retain(|r| r.start() >= min_start);
879 }
880 let (_, start, end) = locus;
881 self.tracker.add((end - start).max(0) as u64);
882 Some(Ok(records))
883 }
884 }
885 }
886}
887
888#[derive(Debug)]
890pub struct LocusEntries<'a> {
891 reader: &'a CramReader,
892 walk: LocusWalk,
893}
894
895impl<'a> LocusEntries<'a> {
896 fn plan(reader: &'a CramReader, req: &EntriesRequest) -> Result<Self> {
897 Ok(Self {
898 reader,
899 walk: LocusWalk::plan(reader, req)?,
900 })
901 }
902 pub fn len(&self) -> usize {
903 self.walk.len()
904 }
905 pub fn is_empty(&self) -> bool {
906 self.walk.is_empty()
907 }
908 pub fn order(&self) -> &[usize] {
909 self.walk.order()
910 }
911}
912
913impl Iterator for LocusEntries<'_> {
914 type Item = Result<Vec<BamRecord>>;
915 fn next(&mut self) -> Option<Self::Item> {
916 self.walk.next_window(self.reader)
917 }
918}
919
920#[derive(Debug)]
922pub struct WindowEntries<'a> {
923 reader: &'a CramReader,
924 walk: LocusWalk,
925}
926
927impl<'a> WindowEntries<'a> {
928 fn plan(reader: &'a CramReader, req: &EntriesRequest, span: i64) -> Result<Self> {
929 Ok(Self {
930 reader,
931 walk: LocusWalk::plan_windows(reader, req, span)?,
932 })
933 }
934 pub fn len(&self) -> usize {
935 self.walk.len()
936 }
937 pub fn is_empty(&self) -> bool {
938 self.walk.is_empty()
939 }
940}
941
942impl Iterator for WindowEntries<'_> {
943 type Item = Result<Vec<BamRecord>>;
944 fn next(&mut self) -> Option<Self::Item> {
945 self.walk.next_window(self.reader)
946 }
947}