use std::io;
use std::path::Path;
use io::Read;
use rusqlite::Connection;
use rusqlite::Transaction;
use rusqlite::params;
use rusqlite::backup::Backup;
use rawzip::ZipArchiveEntryWayfinder;
use rawzip::ZipFileHeaderRecord;
use rawzip::ZipSliceArchive;
use rawzip::time::Utc;
use rawzip::time::ZipDateTime;
use rawzip::time::ZipDateTimeKind;
#[derive(Debug)]
pub enum ZipToSqliteErr {
UnableToInsertEntry(String),
InvalidEntryPath,
InvalidEntryIndex,
UnableToGetEntry,
UnableToStartTransaction(String),
UnableToCommit(String),
InvalidZipArchive(String),
UnableToPersist(String),
UnableToOpenDb(String),
UnableToOpenInMemDb(String),
UnableToReadZip(String),
UnableToCreateTable(String),
}
pub struct ZipEntry<'a> {
pub name: &'a str,
pub mode: u32,
pub mtime: i64,
pub sz: i64,
pub data: &'a [u8],
}
impl<'a> ZipEntry<'a> {
pub fn to_sqlite(&self, con: &Transaction) -> Result<(), ZipToSqliteErr> {
con.execute(
r#"
INSERT INTO sqlar
VALUES (
?1, -- name
?2, -- mode
?3, -- mtime
?4, -- sz
?5 -- data
)
"#,
params![self.name, self.mode, self.mtime, self.sz, self.data],
)
.map_err(|e| ZipToSqliteErr::UnableToInsertEntry(format!("{e}")))
.map(|_| ())
}
}
pub struct ZipArchive<'a> {
inner: ZipSliceArchive<&'a [u8]>,
}
impl<'a> ZipArchive<'a> {
pub fn data(&self, index: ZipArchiveEntryWayfinder) -> Result<&[u8], ZipToSqliteErr> {
let entry = self
.inner
.get_entry(index)
.map_err(|_| ZipToSqliteErr::InvalidEntryIndex)?;
Ok(entry.data())
}
}
impl<'a> ZipArchive<'a> {
pub fn to_sqlite(&self, con: &mut Connection) -> Result<(), ZipToSqliteErr> {
let tx = con
.transaction()
.map_err(|e| ZipToSqliteErr::UnableToStartTransaction(format!("{e}")))?;
tx.execute(
r#"
CREATE TABLE IF NOT EXISTS sqlar (
name TEXT PRIMARY KEY,
mode INT,
mtime INT,
sz INT,
data BLOB
)
"#,
[],
)
.map_err(|e| ZipToSqliteErr::UnableToCreateTable(format!("{e}")))?;
let entries = self.inner.entries();
for rhdr in entries {
let hdr = rhdr.map_err(|_| ZipToSqliteErr::UnableToGetEntry)?;
let index: ZipArchiveEntryWayfinder = hdr.wayfinder();
let zh = ZipItemHeader { inner: hdr };
let name: &str = zh.name()?;
let mode: u32 = zh.mode();
let mtime: i64 = zh
.unixtime()
.or_else(ZipItemHeader::zip_epoch)
.unwrap_or_default();
let data: &[u8] = self.data(index)?;
let sz: i64 = data.len() as i64;
let ent = ZipEntry {
name,
mode,
mtime,
sz,
data,
};
ent.to_sqlite(&tx)?;
}
tx.commit()
.map_err(|e| ZipToSqliteErr::UnableToCommit(format!("{e}")))?;
Ok(())
}
}
pub struct ZipItemHeader<'a> {
inner: ZipFileHeaderRecord<'a>,
}
impl<'a> ZipItemHeader<'a> {
pub fn name(&self) -> Result<&str, ZipToSqliteErr> {
let fpath = self.inner.file_path();
let raw: &[u8] = fpath.as_bytes();
std::str::from_utf8(raw).map_err(|_| ZipToSqliteErr::InvalidEntryPath)
}
pub fn mode(&self) -> u32 {
self.inner.mode().value()
}
pub fn unixtime(&self) -> Option<i64> {
let modified = self.inner.last_modified();
match modified {
ZipDateTimeKind::Utc(u) => Some(u.to_unix()),
ZipDateTimeKind::Local(_) => None,
}
}
pub fn zip_epoch() -> Option<i64> {
let zdt: Option<_> = ZipDateTime::<Utc>::from_components(1980, 1, 1, 0, 0, 0, 0);
zdt.map(|z| z.to_unix())
}
}
pub fn bytes2zip2sqlite(zbytes: &[u8], con: &mut Connection) -> Result<(), ZipToSqliteErr> {
let zs: ZipSliceArchive<_> = rawzip::ZipArchive::<()>::from_slice(zbytes)
.map_err(|e| ZipToSqliteErr::InvalidZipArchive(format!("{e}")))?;
let z = ZipArchive { inner: zs };
z.to_sqlite(con)
}
pub fn mem2f(mem: &Connection, f: &mut Connection) -> Result<(), ZipToSqliteErr> {
let bkup = Backup::new(mem, f).map_err(|e| ZipToSqliteErr::UnableToPersist(format!("{e}")))?;
bkup.run_to_completion(4, core::time::Duration::from_millis(500), None)
.map_err(|e| ZipToSqliteErr::UnableToPersist(format!("{e}")))?;
Ok(())
}
pub fn mem2file<P>(mem: &Connection, filename: P) -> Result<(), ZipToSqliteErr>
where
P: AsRef<Path>,
{
let mut con: Connection =
Connection::open(&filename).map_err(|e| ZipToSqliteErr::UnableToOpenDb(format!("{e}")))?;
mem2f(mem, &mut con)
}
pub fn bytes2zip2sqlite2mem(zbytes: &[u8]) -> Result<Connection, ZipToSqliteErr> {
let mut con: Connection = Connection::open_in_memory()
.map_err(|e| ZipToSqliteErr::UnableToOpenInMemDb(format!("{e}")))?;
bytes2zip2sqlite(zbytes, &mut con)?;
Ok(con)
}
pub fn bytes2zip2sqlite2file<P>(bytes: &[u8], filename: P) -> Result<(), ZipToSqliteErr>
where
P: AsRef<Path>,
{
let saved: Connection = bytes2zip2sqlite2mem(bytes)?;
mem2file(&saved, filename)
}
pub fn reader2bytes<R>(rdr: R, limit: u64) -> Result<Vec<u8>, ZipToSqliteErr>
where
R: Read,
{
let mut limited = rdr.take(limit);
let mut buf: Vec<u8> = vec![];
limited
.read_to_end(&mut buf)
.map_err(|e| ZipToSqliteErr::UnableToReadZip(format!("{e}")))?;
Ok(buf)
}
pub fn stdin2bytes(limit: u64) -> Result<Vec<u8>, ZipToSqliteErr> {
reader2bytes(io::stdin().lock(), limit)
}
pub fn stdin2bytes2zip2sqlite2file<P>(limit: u64, filename: P) -> Result<(), ZipToSqliteErr>
where
P: AsRef<Path>,
{
let zbytes: Vec<u8> = stdin2bytes(limit)?;
bytes2zip2sqlite2file(&zbytes, filename)
}