docufort/
content_reader.rs

1//! This module provides a helper function to find all the content written between two time stamps.
2//!
3//! If nothing else it demonstrates how to loop through documents and read content from a docufort file.
4
5use std::{io::{Read, Seek, SeekFrom, Write}, ops::RangeBounds};
6
7use crate::{core::{BlockState, BlockInputs, Block, Content}, read::read_magic_number, recovery::{try_read_block, BlockReadSummary}, FILE_HEADER_LEN, MAGIC_NUMBER, ReadWriteError, ECC_LEN};
8
9/// This function will read a docufort file and return all the content written between two time stamps.
10///
11/// # Arguments
12/// * `file` - Some sort of Read+Write+Seek object that represents the docufort file.
13/// * `start_hint` - Should be a BlockStart header position from which we want to start reading content.
14/// * `range` - The range of time stamps we want content from.
15///
16/// # Returns
17/// A vector of tuples containing the time stamp and content summaries that can be read using [read_content](crate::read::read_content).
18///
19/// # Notes
20/// * The range will only return content *written* in the range of the given time stamp, not all items within a block that has a start time in this range.
21/// * This function assumes all header timestamps are monotonically increasing.
22/// * This does no ECC at all (you should have integrity checked already).
23///
24/// Recommended: Run integrity check on startup and provide a start_hint for the first block we want content from.
25pub fn find_content<RW:Read+Write+Seek,B:BlockInputs,T:RangeBounds<u64>>(file: &mut RW, start_hint: Option<u64>,range:Option<T>) -> Result<Vec<(u64,Content)>, ReadWriteError> {
26    let mut content = Vec::new();
27    if let Some(s) = start_hint {
28        file.seek(SeekFrom::Start(s))?;
29    }else{
30        file.seek(SeekFrom::Start(FILE_HEADER_LEN as u64 + MAGIC_NUMBER.len() as u64 + ECC_LEN as u64))?;//first block start
31    }
32
33    let range =range.map(|u|{
34        match (u.start_bound(),u.end_bound()){
35            (std::ops::Bound::Included(a), std::ops::Bound::Included(b)) => a.to_be_bytes()..=b.to_be_bytes(),
36            (std::ops::Bound::Included(a), std::ops::Bound::Excluded(b)) => a.to_be_bytes()..=(b-1).to_be_bytes(),
37            (std::ops::Bound::Included(a), std::ops::Bound::Unbounded) =>a.to_be_bytes()..=u64::MAX.to_be_bytes(),
38            (std::ops::Bound::Excluded(a), std::ops::Bound::Included(b)) => (a+1).to_be_bytes()..=b.to_be_bytes(),
39            (std::ops::Bound::Excluded(a), std::ops::Bound::Excluded(b)) => (a+1).to_be_bytes()..=(b-1).to_be_bytes(),
40            (std::ops::Bound::Excluded(a), std::ops::Bound::Unbounded) => (a+1).to_be_bytes()..=u64::MAX.to_be_bytes(),
41            (std::ops::Bound::Unbounded, std::ops::Bound::Included(b)) => 0u64.to_be_bytes()..=b.to_be_bytes(),
42            (std::ops::Bound::Unbounded, std::ops::Bound::Excluded(b)) => 0u64.to_be_bytes()..=(b-1).to_be_bytes(),
43            (std::ops::Bound::Unbounded, std::ops::Bound::Unbounded) => 0u64.to_be_bytes()..=u64::MAX.to_be_bytes(),
44        }
45    });
46
47    //we read from where we are. if there is a range we only capture if it is in range
48    //if we are less than range, we proceed
49    //if we are past range, we return.
50    //we also return if we can't decode a block.
51    //we do no ECC
52
53    'outer: loop {
54        let bs = try_read_block::<_, B>(file, false,false)?;
55        match bs {
56            BlockState::Closed(BlockReadSummary { block, .. }) => {
57                match block {
58                    Block::A { middle,start,.. } => {
59                        let start_time = start.time_stamp();
60                        let ts = u64::from_be_bytes(start_time);
61                        if let Some(r) = range.as_ref() {
62                            if r.contains(&start_time){
63                                content.push((ts,middle))
64                            }else {
65                                match r.end_bound(){
66                                    std::ops::Bound::Included(x) if &start_time > x => break,
67                                    std::ops::Bound::Excluded(x) if &start_time >= x => break,
68                                    _ => (),
69                                }
70                            }
71                        }else{
72                            content.push((ts,middle))
73                        }
74                    },
75                    Block::B { middle, .. } => {
76                        for (s,m) in middle {
77                            let start_time = s.time_stamp();
78                            let ts = u64::from_be_bytes(start_time);
79                            if let Some(r) = range.as_ref() {
80                                if r.contains(&start_time){
81                                    content.push((ts,m))
82                                }else {
83                                    match r.end_bound(){
84                                        std::ops::Bound::Included(x) if &start_time > x => break 'outer,
85                                        std::ops::Bound::Excluded(x) if &start_time >= x => break 'outer,
86                                        _ => (),
87                                    }
88                                }
89                            }else{
90                                content.push((ts,m))
91                            }
92                        }
93                    },
94                }
95            },
96            BlockState::OpenBBlock { content:middle, .. } => {
97                for (s,m) in middle {
98                    let start_time = s.time_stamp();
99                    let ts = u64::from_be_bytes(start_time);
100                    if let Some(r) = range.as_ref() {
101                        if r.contains(&start_time){
102                            content.push((ts,m))
103                        }else {
104                            match r.end_bound(){
105                                std::ops::Bound::Included(x) if &start_time > x => break 'outer,
106                                std::ops::Bound::Excluded(x) if &start_time >= x => break 'outer,
107                                _ => (),
108                            }
109                        }
110                    }else{
111                        content.push((ts,m))
112                    }
113                }
114            }
115            _ => break,
116        }
117        let res = read_magic_number(file, false);
118        if res.is_err(){break}
119    }
120    Ok(content)
121}