Skip to main content

rs_zip2sqlite/
lib.rs

1use std::io;
2use std::path::Path;
3
4use io::Read;
5
6use rusqlite::Connection;
7use rusqlite::Transaction;
8use rusqlite::params;
9
10use rusqlite::backup::Backup;
11
12use rawzip::ZipArchiveEntryWayfinder;
13use rawzip::ZipFileHeaderRecord;
14use rawzip::ZipSliceArchive;
15
16use rawzip::time::Utc;
17use rawzip::time::ZipDateTime;
18use rawzip::time::ZipDateTimeKind;
19
20#[derive(Debug)]
21pub enum ZipToSqliteErr {
22    UnableToInsertEntry(String),
23    InvalidEntryPath,
24    InvalidEntryIndex,
25    UnableToGetEntry,
26    UnableToStartTransaction(String),
27    UnableToCommit(String),
28    InvalidZipArchive(String),
29    UnableToPersist(String),
30    UnableToOpenDb(String),
31    UnableToOpenInMemDb(String),
32    UnableToReadZip(String),
33    UnableToCreateTable(String),
34}
35
36pub struct ZipEntry<'a> {
37    pub name: &'a str,
38
39    /// Access permissions
40    pub mode: u32,
41
42    /// Unixtime in seconds
43    pub mtime: i64,
44
45    /// The original file size
46    pub sz: i64,
47
48    pub data: &'a [u8],
49}
50
51impl<'a> ZipEntry<'a> {
52    pub fn to_sqlite(&self, con: &Transaction) -> Result<(), ZipToSqliteErr> {
53        con.execute(
54            r#"
55                INSERT INTO sqlar
56                VALUES (
57                    ?1, -- name
58                    ?2, -- mode
59                    ?3, -- mtime
60                    ?4, -- sz
61                    ?5  -- data
62                )
63            "#,
64            params![self.name, self.mode, self.mtime, self.sz, self.data],
65        )
66        .map_err(|e| ZipToSqliteErr::UnableToInsertEntry(format!("{e}")))
67        .map(|_| ())
68    }
69}
70
71pub struct ZipArchive<'a> {
72    inner: ZipSliceArchive<&'a [u8]>,
73}
74
75impl<'a> ZipArchive<'a> {
76    pub fn data(&self, index: ZipArchiveEntryWayfinder) -> Result<&[u8], ZipToSqliteErr> {
77        let entry = self
78            .inner
79            .get_entry(index)
80            .map_err(|_| ZipToSqliteErr::InvalidEntryIndex)?;
81        Ok(entry.data())
82    }
83}
84
85impl<'a> ZipArchive<'a> {
86    pub fn to_sqlite(&self, con: &mut Connection) -> Result<(), ZipToSqliteErr> {
87        let tx = con
88            .transaction()
89            .map_err(|e| ZipToSqliteErr::UnableToStartTransaction(format!("{e}")))?;
90
91        tx.execute(
92            r#"
93                CREATE TABLE IF NOT EXISTS sqlar (
94                    name TEXT PRIMARY KEY,
95                    mode INT,
96                    mtime INT,
97                    sz INT,
98                    data BLOB
99                )
100            "#,
101            [],
102        )
103        .map_err(|e| ZipToSqliteErr::UnableToCreateTable(format!("{e}")))?;
104
105        let entries = self.inner.entries();
106        for rhdr in entries {
107            let hdr = rhdr.map_err(|_| ZipToSqliteErr::UnableToGetEntry)?;
108            let index: ZipArchiveEntryWayfinder = hdr.wayfinder();
109            let zh = ZipItemHeader { inner: hdr };
110
111            let name: &str = zh.name()?;
112            let mode: u32 = zh.mode();
113            let mtime: i64 = zh
114                .unixtime()
115                .or_else(ZipItemHeader::zip_epoch)
116                .unwrap_or_default();
117            let data: &[u8] = self.data(index)?;
118            let sz: i64 = data.len() as i64;
119
120            let ent = ZipEntry {
121                name,
122                mode,
123                mtime,
124                sz,
125                data,
126            };
127
128            ent.to_sqlite(&tx)?;
129        }
130
131        tx.commit()
132            .map_err(|e| ZipToSqliteErr::UnableToCommit(format!("{e}")))?;
133        Ok(())
134    }
135}
136
137pub struct ZipItemHeader<'a> {
138    inner: ZipFileHeaderRecord<'a>,
139}
140
141impl<'a> ZipItemHeader<'a> {
142    pub fn name(&self) -> Result<&str, ZipToSqliteErr> {
143        let fpath = self.inner.file_path();
144        let raw: &[u8] = fpath.as_bytes();
145        std::str::from_utf8(raw).map_err(|_| ZipToSqliteErr::InvalidEntryPath)
146    }
147
148    pub fn mode(&self) -> u32 {
149        self.inner.mode().value()
150    }
151
152    pub fn unixtime(&self) -> Option<i64> {
153        let modified = self.inner.last_modified();
154        match modified {
155            ZipDateTimeKind::Utc(u) => Some(u.to_unix()),
156            ZipDateTimeKind::Local(_) => None,
157        }
158    }
159
160    pub fn zip_epoch() -> Option<i64> {
161        let zdt: Option<_> = ZipDateTime::<Utc>::from_components(1980, 1, 1, 0, 0, 0, 0);
162        zdt.map(|z| z.to_unix())
163    }
164}
165
166pub fn bytes2zip2sqlite(zbytes: &[u8], con: &mut Connection) -> Result<(), ZipToSqliteErr> {
167    let zs: ZipSliceArchive<_> = rawzip::ZipArchive::<()>::from_slice(zbytes)
168        .map_err(|e| ZipToSqliteErr::InvalidZipArchive(format!("{e}")))?;
169    let z = ZipArchive { inner: zs };
170    z.to_sqlite(con)
171}
172
173pub fn mem2f(mem: &Connection, f: &mut Connection) -> Result<(), ZipToSqliteErr> {
174    let bkup = Backup::new(mem, f).map_err(|e| ZipToSqliteErr::UnableToPersist(format!("{e}")))?;
175    bkup.run_to_completion(4, core::time::Duration::from_millis(500), None)
176        .map_err(|e| ZipToSqliteErr::UnableToPersist(format!("{e}")))?;
177    Ok(())
178}
179
180pub fn mem2file<P>(mem: &Connection, filename: P) -> Result<(), ZipToSqliteErr>
181where
182    P: AsRef<Path>,
183{
184    let mut con: Connection =
185        Connection::open(&filename).map_err(|e| ZipToSqliteErr::UnableToOpenDb(format!("{e}")))?;
186    mem2f(mem, &mut con)
187}
188
189pub fn bytes2zip2sqlite2mem(zbytes: &[u8]) -> Result<Connection, ZipToSqliteErr> {
190    let mut con: Connection = Connection::open_in_memory()
191        .map_err(|e| ZipToSqliteErr::UnableToOpenInMemDb(format!("{e}")))?;
192    bytes2zip2sqlite(zbytes, &mut con)?;
193    Ok(con)
194}
195
196pub fn bytes2zip2sqlite2file<P>(bytes: &[u8], filename: P) -> Result<(), ZipToSqliteErr>
197where
198    P: AsRef<Path>,
199{
200    let saved: Connection = bytes2zip2sqlite2mem(bytes)?;
201    mem2file(&saved, filename)
202}
203
204pub fn reader2bytes<R>(rdr: R, limit: u64) -> Result<Vec<u8>, ZipToSqliteErr>
205where
206    R: Read,
207{
208    let mut limited = rdr.take(limit);
209    let mut buf: Vec<u8> = vec![];
210    limited
211        .read_to_end(&mut buf)
212        .map_err(|e| ZipToSqliteErr::UnableToReadZip(format!("{e}")))?;
213    Ok(buf)
214}
215
216pub fn stdin2bytes(limit: u64) -> Result<Vec<u8>, ZipToSqliteErr> {
217    reader2bytes(io::stdin().lock(), limit)
218}
219
220pub fn stdin2bytes2zip2sqlite2file<P>(limit: u64, filename: P) -> Result<(), ZipToSqliteErr>
221where
222    P: AsRef<Path>,
223{
224    let zbytes: Vec<u8> = stdin2bytes(limit)?;
225    bytes2zip2sqlite2file(&zbytes, filename)
226}