pijul_core/change/
change_file.rs1use super::*;
2
3pub 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 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 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}