ddup_bak/archive/
entries.rs

1use super::CompressionFormat;
2use flate2::read::{DeflateDecoder, GzDecoder};
3use positioned_io::ReadAt;
4use std::{
5    fmt::{Debug, Formatter},
6    fs::File,
7    io::Read,
8    ops::Deref,
9    sync::Arc,
10    time::SystemTime,
11};
12
13#[derive(Clone, Copy)]
14pub struct EntryMode(u32);
15
16impl EntryMode {
17    #[inline]
18    pub const fn new(mode: u32) -> Self {
19        Self(mode)
20    }
21
22    /// Returns the mode bits.
23    #[inline]
24    pub const fn bits(&self) -> u32 {
25        self.0
26    }
27
28    /// Sets the mode bits.
29    #[inline]
30    pub const fn set_bits(&mut self, mode: u32) {
31        self.0 = mode;
32    }
33
34    /// Returns the user permissions (read, write, execute).
35    #[inline]
36    pub const fn user(&self) -> (bool, bool, bool) {
37        (
38            self.0 & 0o400 != 0,
39            self.0 & 0o200 != 0,
40            self.0 & 0o100 != 0,
41        )
42    }
43
44    /// Sets the user permissions (read, write, execute).
45    #[inline]
46    pub const fn set_user(&mut self, read: bool, write: bool, execute: bool) {
47        self.0 &= !0o700;
48        self.0 |= (read as u32) << 6 | (write as u32) << 5 | (execute as u32) << 4;
49    }
50
51    /// Returns the group permissions (read, write, execute).
52    #[inline]
53    pub const fn group(&self) -> (bool, bool, bool) {
54        (
55            self.0 & 0o040 != 0,
56            self.0 & 0o020 != 0,
57            self.0 & 0o010 != 0,
58        )
59    }
60
61    /// Sets the group permissions (read, write, execute).
62    #[inline]
63    pub const fn set_group(&mut self, read: bool, write: bool, execute: bool) {
64        self.0 &= !0o070;
65        self.0 |= (read as u32) << 3 | (write as u32) << 2 | (execute as u32) << 1;
66    }
67
68    /// Returns the other permissions (read, write, execute).
69    #[inline]
70    pub const fn other(&self) -> (bool, bool, bool) {
71        (
72            self.0 & 0o004 != 0,
73            self.0 & 0o002 != 0,
74            self.0 & 0o001 != 0,
75        )
76    }
77
78    /// Sets the other permissions (read, write, execute).
79    #[inline]
80    pub const fn set_other(&mut self, read: bool, write: bool, execute: bool) {
81        self.0 &= !0o007;
82        self.0 |= (read as u32) | (write as u32) << 1 | (execute as u32) << 2;
83    }
84}
85
86impl Debug for EntryMode {
87    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88        let mut mode = String::with_capacity(9);
89
90        mode.push(if self.0 & 0o400 != 0 { 'r' } else { '-' });
91        mode.push(if self.0 & 0o200 != 0 { 'w' } else { '-' });
92        mode.push(if self.0 & 0o100 != 0 { 'x' } else { '-' });
93        mode.push(if self.0 & 0o040 != 0 { 'r' } else { '-' });
94        mode.push(if self.0 & 0o020 != 0 { 'w' } else { '-' });
95        mode.push(if self.0 & 0o010 != 0 { 'x' } else { '-' });
96        mode.push(if self.0 & 0o004 != 0 { 'r' } else { '-' });
97        mode.push(if self.0 & 0o002 != 0 { 'w' } else { '-' });
98        mode.push(if self.0 & 0o001 != 0 { 'x' } else { '-' });
99
100        write!(f, "{} ({:o})", mode, self.0)
101    }
102}
103
104impl Default for EntryMode {
105    #[inline]
106    fn default() -> Self {
107        Self(0o644)
108    }
109}
110
111impl From<u32> for EntryMode {
112    #[inline]
113    fn from(mode: u32) -> Self {
114        Self(mode)
115    }
116}
117
118impl From<EntryMode> for u32 {
119    #[inline]
120    fn from(mode: EntryMode) -> Self {
121        mode.0
122    }
123}
124
125impl From<std::fs::Permissions> for EntryMode {
126    #[inline]
127    fn from(permissions: std::fs::Permissions) -> Self {
128        #[cfg(unix)]
129        {
130            use std::os::unix::fs::PermissionsExt;
131
132            Self(permissions.mode())
133        }
134        #[cfg(not(unix))]
135        {
136            Self(if permissions.readonly() { 0o444 } else { 0o666 })
137        }
138    }
139}
140
141impl From<EntryMode> for std::fs::Permissions {
142    #[inline]
143    fn from(permissions: EntryMode) -> std::fs::Permissions {
144        #[cfg(unix)]
145        {
146            use std::os::unix::fs::PermissionsExt;
147
148            std::fs::Permissions::from_mode(permissions.0)
149        }
150        #[cfg(not(unix))]
151        {
152            let mut fs_permissions: std::fs::Permissions = unsafe { std::mem::zeroed() };
153            fs_permissions.set_readonly(permissions.0 & 0o444 != 0);
154
155            fs_permissions
156        }
157    }
158}
159
160impl Deref for EntryMode {
161    type Target = u32;
162
163    #[inline]
164    fn deref(&self) -> &Self::Target {
165        &self.0
166    }
167}
168
169pub struct FileEntry {
170    pub name: String,
171    pub mode: EntryMode,
172    pub owner: (u32, u32),
173    pub mtime: SystemTime,
174
175    pub compression: CompressionFormat,
176    pub size_compressed: Option<u64>,
177    pub size_real: u64,
178    pub size: u64,
179
180    pub file: Arc<File>,
181    pub offset: u64,
182    pub decoder: Option<Box<dyn Read + Sync + Send>>,
183    pub consumed: u64,
184}
185
186impl Clone for FileEntry {
187    fn clone(&self) -> Self {
188        Self {
189            name: self.name.clone(),
190            mode: self.mode,
191            owner: self.owner,
192            mtime: self.mtime,
193            compression: self.compression,
194            size_compressed: self.size_compressed,
195            size_real: self.size_real,
196            size: self.size,
197            file: Arc::clone(&self.file),
198            decoder: None,
199            offset: self.offset,
200            consumed: 0,
201        }
202    }
203}
204
205impl Debug for FileEntry {
206    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
207        f.debug_struct("FileEntry")
208            .field("name", &self.name)
209            .field("mode", &self.mode)
210            .field("owner", &self.owner)
211            .field("mtime", &self.mtime)
212            .field("offset", &self.offset)
213            .field("compression", &self.compression)
214            .field("size", &self.size)
215            .field("size_real", &self.size_real)
216            .field("size_compressed", &self.size_compressed)
217            .finish()
218    }
219}
220
221impl Read for FileEntry {
222    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
223        if self.consumed >= self.size {
224            return Ok(0);
225        }
226
227        let remaining = self.size - self.consumed;
228
229        match self.compression {
230            CompressionFormat::None => {
231                let bytes_read = self.file.read_at(self.offset + self.consumed, buf)?;
232
233                if bytes_read > remaining as usize {
234                    self.consumed += remaining;
235                    return Ok(remaining as usize);
236                }
237
238                self.consumed += bytes_read as u64;
239                Ok(bytes_read)
240            }
241            CompressionFormat::Gzip => {
242                if self.decoder.is_none() {
243                    let reader = BoundedReader {
244                        file: Arc::clone(&self.file),
245                        offset: self.offset,
246                        position: 0,
247                        size: self.size_compressed.unwrap(),
248                    };
249
250                    let decoder = Box::new(GzDecoder::new(reader));
251                    self.decoder = Some(decoder);
252                }
253
254                let decoder = self.decoder.as_mut().unwrap();
255                let bytes_read = decoder.read(buf)?;
256
257                if bytes_read > remaining as usize {
258                    self.decoder = None;
259                    self.consumed += remaining;
260                    return Ok(remaining as usize);
261                }
262
263                self.consumed += bytes_read as u64;
264                Ok(bytes_read)
265            }
266            CompressionFormat::Deflate => {
267                if self.decoder.is_none() {
268                    let reader = BoundedReader {
269                        file: Arc::clone(&self.file),
270                        offset: self.offset,
271                        position: 0,
272                        size: self.size_compressed.unwrap(),
273                    };
274
275                    let decoder = Box::new(DeflateDecoder::new(reader));
276                    self.decoder = Some(decoder);
277                }
278
279                let decoder = self.decoder.as_mut().unwrap();
280                let bytes_read = decoder.read(buf)?;
281
282                if bytes_read > remaining as usize {
283                    self.decoder = None;
284                    self.consumed += remaining;
285                    return Ok(remaining as usize);
286                }
287
288                self.consumed += bytes_read as u64;
289                Ok(bytes_read)
290            }
291
292            #[cfg(feature = "brotli")]
293            CompressionFormat::Brotli => {
294                if self.decoder.is_none() {
295                    let reader = BoundedReader {
296                        file: Arc::clone(&self.file),
297                        offset: self.offset,
298                        position: 0,
299                        size: self.size_compressed.unwrap(),
300                    };
301
302                    let decoder = Box::new(brotli::Decompressor::new(reader, 4096));
303                    self.decoder = Some(decoder);
304                }
305
306                let decoder = self.decoder.as_mut().unwrap();
307                let bytes_read = decoder.read(buf)?;
308
309                if bytes_read > remaining as usize {
310                    self.decoder = None;
311                    self.consumed += remaining;
312                    return Ok(remaining as usize);
313                }
314
315                self.consumed += bytes_read as u64;
316                Ok(bytes_read)
317            }
318            #[cfg(not(feature = "brotli"))]
319            CompressionFormat::Brotli => Err(std::io::Error::new(
320                std::io::ErrorKind::InvalidData,
321                "Brotli support is not enabled. Please enable the 'brotli' feature.",
322            )),
323        }
324    }
325}
326
327#[derive(Clone, Debug)]
328pub struct DirectoryEntry {
329    pub name: String,
330    pub mode: EntryMode,
331    pub owner: (u32, u32),
332    pub mtime: SystemTime,
333    pub entries: Vec<Entry>,
334}
335
336#[derive(Clone, Debug)]
337pub struct SymlinkEntry {
338    pub name: String,
339    pub mode: EntryMode,
340    pub owner: (u32, u32),
341    pub mtime: SystemTime,
342    pub target: String,
343    pub target_dir: bool,
344}
345
346#[derive(Clone, Debug)]
347pub enum Entry {
348    File(Box<FileEntry>),
349    Directory(Box<DirectoryEntry>),
350    Symlink(Box<SymlinkEntry>),
351}
352
353impl Entry {
354    /// Returns the name of the entry.
355    /// This is the name of the file or directory, not the full path.
356    /// For example, if the entry is under `path/to/file.txt`, this will return `file.txt`.
357    #[inline]
358    pub const fn name(&self) -> &str {
359        match self {
360            Entry::File(entry) => entry.name.as_str(),
361            Entry::Directory(entry) => entry.name.as_str(),
362            Entry::Symlink(entry) => entry.name.as_str(),
363        }
364    }
365
366    /// Returns the mode of the entry.
367    /// This also contains the file permissions of the entry.
368    #[inline]
369    pub const fn mode(&self) -> EntryMode {
370        match self {
371            Entry::File(entry) => entry.mode,
372            Entry::Directory(entry) => entry.mode,
373            Entry::Symlink(entry) => entry.mode,
374        }
375    }
376
377    /// Returns the owner of the entry.
378    /// This is the user ID and group ID of the entry.
379    #[inline]
380    pub const fn owner(&self) -> (u32, u32) {
381        match self {
382            Entry::File(entry) => entry.owner,
383            Entry::Directory(entry) => entry.owner,
384            Entry::Symlink(entry) => entry.owner,
385        }
386    }
387
388    /// Returns the modification time of the entry.
389    /// This is the time the entry was last modified.
390    #[inline]
391    pub const fn mtime(&self) -> SystemTime {
392        match self {
393            Entry::File(entry) => entry.mtime,
394            Entry::Directory(entry) => entry.mtime,
395            Entry::Symlink(entry) => entry.mtime,
396        }
397    }
398
399    #[inline]
400    pub const fn is_file(&self) -> bool {
401        matches!(self, Entry::File(_))
402    }
403
404    #[inline]
405    pub const fn is_directory(&self) -> bool {
406        matches!(self, Entry::Directory(_))
407    }
408
409    #[inline]
410    pub const fn is_symlink(&self) -> bool {
411        matches!(self, Entry::Symlink(_))
412    }
413}
414
415struct BoundedReader {
416    file: Arc<File>,
417    offset: u64,
418    size: u64,
419    position: u64,
420}
421
422impl Read for BoundedReader {
423    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
424        if self.position >= self.size {
425            return Ok(0);
426        }
427
428        let remaining = self.size - self.position;
429        let to_read = std::cmp::min(buf.len(), remaining as usize);
430
431        let bytes_read = self
432            .file
433            .read_at(self.offset + self.position, &mut buf[..to_read])?;
434        self.position += bytes_read as u64;
435
436        Ok(bytes_read)
437    }
438}