crow/src/lib.rs
2022-07-01 19:27:28 -03:00

238 lines
6.3 KiB
Rust

use blake2::Digest;
use lmdb::{traits::FromLmdbBytes, Database, Environment};
use lmdb_zero as lmdb;
use tiny_http::Request;
use std::{str::FromStr, sync::Arc};
use thiserror::Error;
pub trait Handler: Send + Sync {
fn respond<'u>(
&self,
req: Request,
db: &DatabaseContext,
params: matchit::Params<'u, 'u>,
) -> CrowResult<()>;
}
pub mod routes;
#[derive(Error, Debug)]
pub enum CrowError {
#[error(transparent)]
LmdbError(#[from] lmdb::Error),
#[error(transparent)]
IOError(#[from] std::io::Error),
#[error(transparent)]
ImageError(#[from] image::error::ImageError),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
pub type CrowResult<T> = Result<T, CrowError>;
#[derive(rkyv::Archive, rkyv::Serialize)]
pub struct FileHeader {
file_name: Option<String>,
content_type: Option<String>,
}
impl FromLmdbBytes for ArchivedFileHeader {
fn from_lmdb_bytes(bytes: &[u8]) -> Result<&Self, String> {
Ok(unsafe { rkyv::archived_root::<FileHeader>(bytes) })
}
}
#[derive(rkyv::Serialize, rkyv::Archive, Copy, Debug, Clone)]
#[archive(compare(PartialEq))]
#[archive_attr(derive(Copy, Clone))]
#[repr(u8)]
pub enum ImageFormat {
PNG,
WEBP,
JPG,
}
impl FromStr for ImageFormat {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"png" | "PNG" => ImageFormat::PNG,
"webp" | "WEBP" => ImageFormat::WEBP,
"jpg" | "JPG" | "jpeg" | "JPEG" => ImageFormat::JPG,
_ => return Err(()),
})
}
}
impl ImageFormat {
pub const fn to_mime(&self) -> &'static str {
match *self {
ImageFormat::PNG => "image/png",
ImageFormat::JPG => "image/jpeg",
ImageFormat::WEBP => "image/webp",
}
}
}
impl From<ImageFormat> for image::ImageFormat {
fn from(val: ImageFormat) -> Self {
match val {
ImageFormat::PNG => image::ImageFormat::Png,
ImageFormat::WEBP => image::ImageFormat::WebP,
ImageFormat::JPG => image::ImageFormat::Jpeg,
}
}
}
impl From<ArchivedImageFormat> for image::ImageFormat {
fn from(val: ArchivedImageFormat) -> Self {
match val {
ArchivedImageFormat::PNG => image::ImageFormat::Png,
ArchivedImageFormat::WEBP => image::ImageFormat::WebP,
ArchivedImageFormat::JPG => image::ImageFormat::Jpeg,
}
}
}
#[derive(rkyv::Serialize, rkyv::Archive)]
pub struct ImageHeader {
store_format: ImageFormat,
}
impl FromLmdbBytes for ArchivedImageHeader {
fn from_lmdb_bytes(bytes: &[u8]) -> Result<&Self, String> {
Ok(unsafe { rkyv::archived_root::<ImageHeader>(bytes) })
}
}
#[derive(Clone)]
pub struct DatabaseContext {
pub env: Arc<Environment>,
pub metadata_store: Arc<Database<'static>>,
pub binary_store: Arc<Database<'static>>,
pub image_store: Arc<Database<'static>>,
pub image_meta_store: Arc<Database<'static>>,
}
impl DatabaseContext {
pub fn create(path: &str) -> CrowResult<DatabaseContext> {
let env = Arc::new(unsafe {
let mut builder = lmdb::EnvBuilder::new()?;
builder.set_maxdbs(4)?;
builder.open(path, lmdb::open::NOTLS, 0o600)?
});
let binary_store = Arc::new(lmdb::Database::open(
env.clone(),
Some("binary"),
&lmdb::DatabaseOptions::new(lmdb::db::CREATE),
)?);
let image_store = Arc::new(lmdb::Database::open(
env.clone(),
Some("image"),
&lmdb::DatabaseOptions::new(lmdb::db::CREATE),
)?);
let metadata_store = Arc::new(lmdb::Database::open(
env.clone(),
Some("metadata"),
&lmdb::DatabaseOptions::new(lmdb::db::CREATE),
)?);
let image_meta_store = Arc::new(lmdb::Database::open(
env.clone(),
Some("metadata-image"),
&lmdb::DatabaseOptions::new(lmdb::db::CREATE),
)?);
Ok(DatabaseContext {
env,
binary_store,
metadata_store,
image_meta_store,
image_store,
})
}
#[inline]
pub fn write_txn(&self) -> CrowResult<lmdb::WriteTransaction<'static>> {
lmdb::WriteTransaction::new(self.env.clone()).map_err(CrowError::from)
}
#[inline]
pub fn read_txn(&self) -> CrowResult<lmdb::ReadTransaction<'static>> {
lmdb::ReadTransaction::new(self.env.clone()).map_err(CrowError::from)
}
}
mod macros {
macro_rules! response {
(err $status:literal $msg:literal) => {
Response::new(
tiny_http::StatusCode($status),
vec![],
$msg.as_bytes(),
None,
None,
)
};
}
macro_rules! single_multipart {
($req:expr) => {
Multipart::from_request($req)
.ok()
.and_then(|v| v.into_entry().into_result().ok())
.flatten()
};
}
macro_rules! fn_to_handler {
($f:ident : $handler_name:ident) => {
pub struct $handler_name;
impl Handler for $handler_name {
fn respond<'u>(
&self,
req: Request,
db: &DatabaseContext,
params: matchit::Params<'u, 'u>,
) -> CrowResult<()> {
$f(req, db, params)
}
}
};
}
macro_rules! parse_base64_hash {
($fr:expr) => {
$fr.and_then(|s| {
let mut out: [u8; 32] = [0; 32];
base64_url::decode_to_slice(s, &mut out).ok()?;
Some(out)
})
};
}
macro_rules! some_or_response {
($opt:expr, or respond to $req:ident with $response:expr) => {
match $opt {
Some(v) => v,
None => {
$req.respond($response)?;
return Ok(());
}
}
};
}
pub(crate) use fn_to_handler;
pub(crate) use parse_base64_hash;
pub(crate) use response;
pub(crate) use single_multipart;
pub(crate) use some_or_response;
}
pub(crate) use macros::*;