hyaenidae/src/images.rs
2021-04-02 12:07:19 -05:00

363 lines
8.6 KiB
Rust

use crate::State;
use actix_web::{http::StatusCode, web, HttpRequest, HttpResponse, ResponseError, Scope};
use awc::Client;
use hyaenidae_profiles::pictrs::ImageInfo;
use hyaenidae_toolkit::Image;
use url::Url;
pub(crate) use hyaenidae_profiles::pictrs::ImageType;
pub(crate) struct IconImage {
key: String,
title: String,
}
pub(crate) struct ThumbnailImage {
key: String,
title: String,
}
pub(crate) struct FullImage {
key: String,
title: String,
}
pub(crate) struct BannerImage {
key: String,
title: String,
}
impl Image for IconImage {
fn src(&self) -> String {
largest_icon(&self.key, ImageType::Png)
}
fn title(&self) -> String {
self.title.clone()
}
fn jpeg_srcset(&self) -> Option<String> {
None
}
fn png_srcset(&self) -> Option<String> {
Some(icon_srcset(&self.key, ImageType::Png))
}
fn webp_srcset(&self) -> Option<String> {
Some(icon_srcset(&self.key, ImageType::Webp))
}
}
impl Image for ThumbnailImage {
fn src(&self) -> String {
largest_thumbnail(&self.key, ImageType::Png)
}
fn title(&self) -> String {
self.title.clone()
}
fn jpeg_srcset(&self) -> Option<String> {
None
}
fn png_srcset(&self) -> Option<String> {
Some(thumbnail_srcset(&self.key, ImageType::Png))
}
fn webp_srcset(&self) -> Option<String> {
Some(thumbnail_srcset(&self.key, ImageType::Webp))
}
}
impl Image for FullImage {
fn src(&self) -> String {
largest_image(&self.key, ImageType::Png)
}
fn title(&self) -> String {
self.title.clone()
}
fn jpeg_srcset(&self) -> Option<String> {
None
}
fn png_srcset(&self) -> Option<String> {
Some(image_srcset(&self.key, ImageType::Png))
}
fn webp_srcset(&self) -> Option<String> {
Some(image_srcset(&self.key, ImageType::Webp))
}
}
impl Image for BannerImage {
fn src(&self) -> String {
largest_banner(&self.key, ImageType::Jpeg)
}
fn title(&self) -> String {
self.title.clone()
}
fn png_srcset(&self) -> Option<String> {
None
}
fn jpeg_srcset(&self) -> Option<String> {
Some(banner_srcset(&self.key, ImageType::Jpeg))
}
fn webp_srcset(&self) -> Option<String> {
Some(banner_srcset(&self.key, ImageType::Webp))
}
}
impl IconImage {
pub(crate) fn new(key: &str, title: &str) -> Self {
IconImage {
key: key.to_owned(),
title: title.to_owned(),
}
}
}
impl ThumbnailImage {
pub(crate) fn new(key: &str, title: &str) -> Self {
ThumbnailImage {
key: key.to_owned(),
title: title.to_owned(),
}
}
}
impl FullImage {
pub(crate) fn new(key: &str, title: &str) -> Self {
FullImage {
key: key.to_owned(),
title: title.to_owned(),
}
}
}
impl BannerImage {
pub(crate) fn new(key: &str, title: &str) -> Self {
BannerImage {
key: key.to_owned(),
title: title.to_owned(),
}
}
}
#[derive(Clone)]
pub(super) struct Images {
base_url: Url,
}
impl Images {
pub(super) fn new(base_url: Url) -> Self {
Images { base_url }
}
}
impl ImageInfo for Images {
fn image_url(&self, key: &str) -> Url {
let mut url = self.base_url.clone();
url.set_path(&format!("/image/full/{}", key));
url
}
}
pub(super) fn scope() -> Scope {
web::scope("/image")
.service(web::resource("/full/{key}").route(web::get().to(serve)))
.service(web::resource("/icon/{size}.{kind}").route(web::get().to(serve_icon)))
.service(web::resource("/banner/{size}.{kind}").route(web::get().to(serve_banner)))
.service(web::resource("/image/{size}.{kind}").route(web::get().to(serve_image)))
.service(web::resource("/thumb/{size}.{kind}").route(web::get().to(serve_thumbnail)))
}
async fn serve(
req: HttpRequest,
key: web::Path<String>,
client: web::Data<Client>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
state.profiles.pictrs.serve_full(&req, &key, &client).await
}
#[derive(Debug, thiserror::Error)]
#[error("Invalid image size requested: {0}px")]
struct SizeError(u64);
impl ResponseError for SizeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest()
.content_type("text/plain")
.body(self.to_string())
}
}
pub(crate) fn image_srcset(key: &str, kind: ImageType) -> String {
VALID_IMAGE_SIZES
.iter()
.map(|size| format!("{} {}w", image_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn icon_srcset(key: &str, kind: ImageType) -> String {
VALID_ICON_SIZES
.iter()
.map(|size| format!("{} {}w", icon_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn thumbnail_srcset(key: &str, kind: ImageType) -> String {
VALID_THUMBNAIL_SIZES
.iter()
.map(|size| format!("{} {}w", thumbnail_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn banner_srcset(key: &str, kind: ImageType) -> String {
VALID_BANNER_SIZES
.iter()
.map(|size| format!("{} {}w", banner_path(key, *size, kind), size))
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn largest_image(key: &str, kind: ImageType) -> String {
image_path(key, 5040, kind)
}
pub(crate) fn largest_banner(key: &str, kind: ImageType) -> String {
banner_path(key, 1680, kind)
}
pub(crate) fn largest_icon(key: &str, kind: ImageType) -> String {
icon_path(key, 512, kind)
}
pub(crate) fn largest_thumbnail(key: &str, kind: ImageType) -> String {
thumbnail_path(key, 512, kind)
}
fn icon_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/icon/{}.{}?src={}", size, kind, key)
}
fn thumbnail_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/thumb/{}.{}?src={}", size, kind, key)
}
fn banner_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/banner/{}.{}?src={}", size, kind, key)
}
fn image_path(key: &str, size: u64, kind: ImageType) -> String {
format!("/image/image/{}.{}?src={}", size, kind, key)
}
#[derive(Clone, Debug, serde::Deserialize)]
pub struct SrcQuery {
src: String,
}
static VALID_IMAGE_SIZES: &[u64] = &[420, 840, 1260, 1680, 2520, 5040];
async fn serve_image(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
client: web::Data<Client>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_IMAGE_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_thumbnail(&req, &src.src, kind, size, &client)
.await
}
static VALID_ICON_SIZES: &[u64] = &[64, 128, 256, 512];
async fn serve_icon(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
client: web::Data<Client>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_ICON_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_icon(&req, &src.src, kind, size, &client)
.await
}
static VALID_THUMBNAIL_SIZES: &[u64] = &[64, 128, 256, 512];
async fn serve_thumbnail(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
client: web::Data<Client>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_THUMBNAIL_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_thumbnail(&req, &src.src, kind, size, &client)
.await
}
static VALID_BANNER_SIZES: &[u64] = &[420, 840, 1260, 1680];
async fn serve_banner(
req: HttpRequest,
path: web::Path<(u64, ImageType)>,
src: web::Query<SrcQuery>,
client: web::Data<Client>,
state: web::Data<State>,
) -> Result<HttpResponse, actix_web::Error> {
let (size, kind) = path.into_inner();
if !VALID_BANNER_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
state
.profiles
.pictrs
.serve_banner(&req, &src.src, kind, size, &client)
.await
}