Skip to main content

pijul_core/change/
change_file.rs

1use super::*;
2
3/// An open, seekable change file.
4pub struct ChangeFile {
5    s: Option<zstd_seekable::Seekable<'static, OffFile>>,
6    hashed: Hashed<Hunk<Option<Hash>, Local>, Author>,
7    hash: Hash,
8    unhashed: Option<toml::Value>,
9}
10
11struct OffFile {
12    f: std::fs::File,
13    start: u64,
14}
15
16unsafe impl Send for OffFile {}
17
18impl std::io::Read for OffFile {
19    fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
20        self.f.read(buf)
21    }
22}
23
24impl std::io::Seek for OffFile {
25    fn seek(&mut self, from: std::io::SeekFrom) -> Result<u64, std::io::Error> {
26        use std::io::SeekFrom;
27        let from = match from {
28            SeekFrom::Start(s) => SeekFrom::Start(s + self.start),
29            c => c,
30        };
31        self.f.seek(from)
32    }
33}
34
35impl ChangeFile {
36    /// Open a change file from a path.
37    pub fn open(hash: Hash, path: &str) -> Result<Self, ChangeError> {
38        use std::io::Read;
39        let mut r = std::fs::File::open(path).map_err(|err| ChangeError::IoHash { err, hash })?;
40        let mut buf = vec![0; Change::OFFSETS_SIZE as usize];
41        r.read_exact(&mut buf)?;
42        let offsets: Offsets = bincode::deserialize(&buf)?;
43        if offsets.version != VERSION && offsets.version != VERSION_NOENC {
44            return Err(ChangeError::VersionMismatch {
45                got: offsets.version,
46            });
47        }
48
49        buf.clear();
50        buf.resize((offsets.unhashed_off - Change::OFFSETS_SIZE) as usize, 0);
51        r.read_exact(&mut buf)?;
52        let mut buf2 = vec![0u8; offsets.hashed_len as usize];
53        let hashed: Hashed<Hunk<Option<Hash>, Local>, Author> = if offsets.version == VERSION {
54            let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
55            s.decompress(&mut buf2, 0)?;
56            trace!("deserialize current version {:?}", buf2.len());
57            bincode::deserialize(&buf2)?
58        } else {
59            assert_eq!(offsets.version, VERSION_NOENC);
60            let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
61            s.decompress(&mut buf2, 0)?;
62            trace!("deserialize noenc {:?}", buf2.len());
63            let h: Hashed<noenc::Hunk<Option<Hash>, Local>, noenc::Author> =
64                bincode::deserialize(&buf2)?;
65            h.into()
66        };
67
68        buf.resize((offsets.contents_off - offsets.unhashed_off) as usize, 0);
69        let unhashed = if buf.is_empty() {
70            None
71        } else {
72            r.read_exact(&mut buf)?;
73            let mut s = zstd_seekable::Seekable::init_buf(&buf)?;
74            buf2.resize(offsets.unhashed_len as usize, 0);
75            s.decompress(&mut buf2, 0)?;
76            trace!("parsing unhashed: {:?}", std::str::from_utf8(&buf2));
77            serde_json::from_slice(&buf2).ok()
78        };
79
80        let m = r.metadata()?;
81        let s = if offsets.contents_off >= m.len() {
82            None
83        } else {
84            Some(zstd_seekable::Seekable::init(Box::new(OffFile {
85                f: r,
86                start: offsets.contents_off,
87            }))?)
88        };
89        Ok(ChangeFile {
90            s,
91            hashed,
92            hash,
93            unhashed,
94        })
95    }
96
97    pub fn has_contents(&self) -> bool {
98        self.s.is_some()
99    }
100
101    /// Reads the contents at an offset into `buf`, and returns the
102    /// number of bytes read. The bounds of the change's "contents"
103    /// section are not checked.
104    pub fn read_contents(&mut self, offset: u64, buf: &mut [u8]) -> Result<usize, ChangeError> {
105        trace!("read_contents {:?} {:?}", offset, buf.len());
106        if let Some(ref mut s) = self.s {
107            Ok(s.decompress(buf, offset)?)
108        } else {
109            Err(ChangeError::MissingContents { hash: self.hash })
110        }
111    }
112
113    pub fn hashed(&self) -> &Hashed<Hunk<Option<Hash>, Local>, Author> {
114        &self.hashed
115    }
116
117    pub fn unhashed(&self) -> &Option<toml::Value> {
118        &self.unhashed
119    }
120}