1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use crate::notifications::Message;
use crate::notifications::Response;
use crate::transformer::FileContext;
use crate::transformer::Transformer;
use crate::transformer::TransformerType;
use anyhow::bail;
use anyhow::Result;
use bytes::BufMut;
use std::time::Duration;
use std::time::SystemTime;
use tar::Header;

pub struct TarEnc {
    header: Option<Header>,
    padding: usize,
    finished: bool,
    init: bool,
}

impl TryFrom<FileContext> for Header {
    type Error = anyhow::Error;

    fn try_from(value: FileContext) -> Result<Self> {
        let mut header = Header::new_gnu();

        let path = match value.file_path {
            Some(p) => p + &value.file_name,
            None => value.file_name,
        };
        header.set_path(path)?;
        header.set_mode(value.mode.unwrap_or(0o644));
        header.set_mtime(value.mtime.unwrap_or_else(|| {
            SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)
                .unwrap_or_else(|_| Duration::from_secs(0))
                .as_secs()
        }));
        header.set_uid(value.gid.unwrap_or(1000));
        header.set_gid(value.gid.unwrap_or(1000));
        header.set_size(value.file_size);
        header.set_cksum();
        Ok(header)
    }
}

impl TarEnc {
    pub fn new() -> TarEnc {
        TarEnc {
            header: None,
            padding: 0,
            finished: false,
            init: true,
        }
    }
}

impl Default for TarEnc {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait::async_trait]
impl Transformer for TarEnc {
    async fn process_bytes(
        &mut self,
        buf: &mut bytes::BytesMut,
        finished: bool,
        should_flush: bool,
    ) -> Result<bool> {
        if should_flush {
            if self.padding > 0 {
                buf.put(vec![0u8; self.padding].as_ref());
            }
            self.padding = 0;
            return Ok(finished);
        }
        if let Some(header) = &self.header {
            let temp = buf.split();
            if self.init {
                self.init = false;
            }
            buf.put(header.as_bytes().as_slice());
            buf.put(temp);
            self.header = None;
        }

        if finished && !self.finished {
            buf.put(vec![0u8; self.padding].as_ref());
            buf.put([0u8; 1024].as_slice());
            self.finished = true;
        }
        Ok(self.finished)
    }

    fn get_type(&self) -> TransformerType {
        TransformerType::TarEncoder
    }

    async fn notify(&mut self, message: &Message) -> Result<Response> {
        if message.target == TransformerType::All {
            if let crate::notifications::MessageData::NextFile(nfile) = &message.data {
                if self.header.is_none() {
                    if nfile.context.is_dir || nfile.context.is_symlink {
                        self.padding = 0;
                    } else {
                        self.padding = 512 - nfile.context.file_size as usize % 512;
                    }
                    self.header = Some(TryInto::<Header>::try_into(nfile.context.clone())?);
                } else {
                    bail!("[TAR] A Header is still present")
                }
                self.finished = false;
            }
        }

        Ok(Response::Ok)
    }
}