applesauce_core/
reader.rs1use crate::decmpfs::{BlockInfo, Storage};
2use crate::{compressor, decmpfs};
3use std::io::{self, BufReader, Cursor, Read, Seek};
4
5pub trait Open {
6 type ResourceFork: Read + Seek;
7
8 fn open_resource_fork(self) -> io::Result<Self::ResourceFork>;
9}
10
11impl<R: Read + Seek, F: FnOnce() -> R> Open for F {
12 type ResourceFork = R;
13
14 #[inline]
15 fn open_resource_fork(self) -> io::Result<Self::ResourceFork> {
16 Ok(self())
17 }
18}
19
20#[derive(Debug)]
21enum State<R> {
22 Xattr(Cursor<Vec<u8>>),
23 ResourceFork {
24 block_infos: Vec<BlockInfo>,
26 last_offset: u32,
27 reader: BufReader<R>,
28 },
29}
30
31#[derive(Debug)]
32pub struct Reader<R> {
33 kind: compressor::Kind,
34 state: State<R>,
35}
36
37impl<R: Read + Seek> Reader<R> {
38 pub fn new<O>(decmpfs_data: &[u8], open: O) -> io::Result<Self>
39 where
40 O: Open<ResourceFork = R>,
41 {
42 let decmpfs_value = decmpfs::Value::from_data(decmpfs_data)?;
43 let (kind, storage) = decmpfs_value
44 .compression_type
45 .compression_storage()
46 .filter(|(kind, _)| kind.supported())
47 .ok_or_else(|| io::Error::other("unsupported compression kind or storage"))?;
48 let state = match storage {
49 Storage::Xattr => State::Xattr(Cursor::new(decmpfs_value.extra_data.to_vec())),
50 Storage::ResourceFork => {
51 let mut rfork = BufReader::new(open.open_resource_fork()?);
52 let mut blocks_info =
53 kind.read_block_info(&mut rfork, decmpfs_value.uncompressed_size)?;
54
55 rfork.rewind()?;
57 blocks_info.reverse();
59
60 State::ResourceFork {
61 block_infos: blocks_info,
62 last_offset: 0,
63 reader: rfork,
64 }
65 }
66 };
67 Ok(Self { kind, state })
68 }
69
70 pub fn read_block_into(&mut self, dst: &mut Vec<u8>) -> io::Result<bool> {
71 match &mut self.state {
72 State::Xattr(cursor) => cursor.read_to_end(dst).map(|bytes_read| bytes_read > 0),
73 State::ResourceFork {
74 block_infos,
75 last_offset,
76 reader,
77 } => {
78 let block = match block_infos.pop() {
79 Some(block) => block,
80 None => return Ok(false),
81 };
82 let diff = i64::from(block.offset) - i64::from(*last_offset);
83 reader.seek_relative(diff)?;
84
85 let bytes_read = reader
86 .by_ref()
87 .take(block.compressed_size.into())
88 .read_to_end(dst)?;
89 if bytes_read < block.compressed_size as usize {
90 return Err(io::ErrorKind::UnexpectedEof.into());
91 }
92 *last_offset = block
93 .offset
94 .checked_add(block.compressed_size)
95 .ok_or(io::ErrorKind::InvalidData)?;
96 Ok(true)
97 }
98 }
99 }
100
101 #[inline]
102 pub fn compression_kind(&self) -> compressor::Kind {
103 self.kind
104 }
105
106 #[inline]
107 pub fn remaining_blocks(&self) -> usize {
108 match &self.state {
109 State::Xattr(cursor) => {
110 let remaining = cursor.get_ref().len() as u64 - cursor.position();
111 usize::from(remaining > 0)
112 }
113 State::ResourceFork { block_infos, .. } => block_infos.len(),
114 }
115 }
116}