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 pub mode: u32,
41
42 pub mtime: i64,
44
45 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}