luciders/src/main.rs

244 lines
8.7 KiB
Rust
Raw Normal View History

2024-11-02 10:05:03 +00:00
use std::{net::TcpListener, process, sync::Arc};
2024-10-29 20:58:10 +00:00
2024-11-01 11:51:12 +00:00
use http::responses::{Body, Response};
2024-10-29 20:58:10 +00:00
2024-11-02 00:56:25 +00:00
use resvg::{
tiny_skia::{Color, Pixmap, Transform},
usvg,
};
mod constants;
mod handlers;
2024-10-29 20:58:10 +00:00
mod http;
2024-11-01 11:51:12 +00:00
mod icons;
2024-11-02 10:05:03 +00:00
mod threads;
2024-10-29 20:58:10 +00:00
fn main() {
println!("luciders starting...");
2024-11-02 06:15:42 +00:00
let port = match std::env::var("PORT") {
Ok(port) => port,
Err(_) => "7878".to_string(),
};
let listener = TcpListener::bind(format!("[::]:{}", port))
.unwrap_or_else(|_| fatal("Failed to bind to address"));
2024-10-29 20:58:10 +00:00
2024-11-02 10:05:03 +00:00
let icons_rc = Arc::new(match icons::Icons::build() {
2024-11-01 11:51:12 +00:00
Ok(icons) => icons,
Err(e) => fatal(e),
2024-11-02 00:56:25 +00:00
});
2024-11-01 11:51:12 +00:00
let mut handlers = handlers::Handlers::new();
2024-10-29 20:58:10 +00:00
handlers.add_handler("/", |req| {
if req.method != "GET" {
return Response::new(req, 405, Body::Static("Method Not Allowed"));
2024-10-29 20:58:10 +00:00
}
Response::new(req, 200, Body::Static("OK - luciders is running"))
});
2024-11-02 06:33:44 +00:00
handlers.add_handler("/info", |req| {
if req.method != "GET" {
return Response::new(req, 405, Body::Static("Method Not Allowed"));
}
2024-11-04 00:14:39 +00:00
Response::new(req, 200, Body::Static(constants::SERVER_INFO))
2024-11-02 06:33:44 +00:00
});
2024-11-02 00:56:25 +00:00
handlers.add_handler("/icons/[icon].svg", {
2024-11-02 10:05:03 +00:00
let icons = Arc::clone(&icons_rc);
2024-11-02 00:56:25 +00:00
move |req| {
if req.method != "GET" {
return Response::new(req, 405, Body::Static("Method Not Allowed"));
}
2024-11-02 00:56:25 +00:00
let icon_filename = match req.url.get_path_segment(1) {
Some(icon) => icon,
None => return Response::new(req, 404, Body::Static("Icon Segment Not Found")),
};
2024-11-02 00:56:25 +00:00
let icon_name = &icon_filename[..icon_filename.len() - icons::ICON_FILE_EXTENSION_LEN];
2024-11-01 11:51:12 +00:00
2024-11-02 00:56:25 +00:00
if !icons.has_iconname(icon_name) {
return Response::new(req, 404, Body::Static("Icon Not Found"));
}
2024-11-02 00:56:25 +00:00
let icon = match icons.get_icon(icon_name) {
Some(icon) => icon,
2024-11-02 06:15:42 +00:00
None => {
error("Failed to read icon");
return Response::new(req, 500, Body::Static("Failed to read icon"));
}
2024-11-02 00:56:25 +00:00
};
2024-11-01 11:51:12 +00:00
2024-11-02 00:56:25 +00:00
return Response::new(req, 200, Body::Bytes(icon, "image/svg+xml"));
}
});
handlers.add_handler("/icons/[icon].png", {
2024-11-02 10:05:03 +00:00
let icons = Arc::clone(&icons_rc);
2024-11-02 00:56:25 +00:00
move |req| {
if req.method != "GET" {
return Response::new(req, 405, Body::Static("Method Not Allowed"));
}
let icon_filename = match req.url.get_path_segment(1) {
Some(icon) => icon,
None => return Response::new(req, 404, Body::Static("Icon Segment Not Found")),
};
let icon_name = &icon_filename[..icon_filename.len() - icons::ICON_FILE_EXTENSION_LEN];
if !icons.has_iconname(icon_name) {
return Response::new(req, 404, Body::Static("Icon Not Found"));
}
2024-11-02 07:04:33 +00:00
let mut icon_string = match icons.get_icon_string(icon_name) {
2024-11-02 00:56:25 +00:00
Some(icon) => icon,
2024-11-02 06:15:42 +00:00
None => {
error("Failed to read icon");
return Response::new(req, 500, Body::Static("Failed to read icon"))
},
2024-11-02 00:56:25 +00:00
};
// Options handling
let mut scale = 1;
match req.url.query.get("scale") {
Some(scale_str) => {
match scale_str.parse::<u32>() {
Ok(scale_val) => {
if scale_val <= 0 || scale_val > 100 {
return Response::new(req, 400, Body::Static("Invalid scale value. Scale value must be an integer > 0 and <= 100"));
}
scale = scale_val;
}
2024-11-02 06:15:42 +00:00
Err(_) => return Response::new(req, 400, Body::Static("Invalid scale value. Scale value must be an integer > 0 and <= 100"))
2024-11-02 00:56:25 +00:00
}
}
None => {}
}
let mut padding = 0;
match req.url.query.get("padding") {
Some(padding_str) => {
match padding_str.parse::<u32>() {
Ok(padding_val) => {
if padding_val > 100 {
return Response::new(req, 400, Body::Static("Invalid padding value. Padding value must be an integer >= 0 and <= 100"));
}
padding = padding_val;
}
2024-11-02 06:15:42 +00:00
Err(_) => return Response::new(req, 400, Body::Static("Invalid padding value. Padding value must be an integer >= 0 and <= 100"))
2024-11-02 00:56:25 +00:00
}
}
None => {}
}
2024-11-02 06:33:44 +00:00
match req.url.query.get("discord_compatibility") {
Some(_) => {
padding = 8;
}
None => {}
}
2024-11-02 01:34:16 +00:00
let mut background = Color::TRANSPARENT;
match req.url.query.get("background") {
Some(background_str) => {
match u32::from_str_radix(&background_str, 16) {
Ok(background_val) => {
if background_val > 0xFFFFFF {
2024-11-02 07:04:33 +00:00
return Response::new(req, 400, Body::Static("Invalid background value. Background value must be a valid hex color #000000 - #FFFFFF"));
2024-11-02 01:34:16 +00:00
}
let red = ((background_val >> 16) & 0xFF) as u8;
let green = ((background_val >> 8) & 0xFF) as u8;
let blue = (background_val & 0xFF) as u8;
background = Color::from_rgba8(red, green, blue, 255);
}
2024-11-02 06:15:42 +00:00
Err(_) => return Response::new(req, 400, Body::Static("Invalid background value. Background value must be a valid hex color"))
2024-11-02 01:34:16 +00:00
}
}
None => {}
}
2024-11-02 07:04:33 +00:00
let mut svg_stroke = "currentColor";
2024-11-02 07:17:02 +00:00
// thx horizell for this code
match (req.url.query.get("stroke_color"), req.url.query.get("stroke_colour")) {
(Some(stroke_str), _) | (_, Some(stroke_str)) => svg_stroke = stroke_str,
_ => {}
}
2024-11-02 07:04:33 +00:00
icon_string = icon_string.replace("currentColor", &svg_stroke);
let mut backwards_compatibility = false;
match req.url.query.get("backwards_compatibility") {
Some(_) => {
backwards_compatibility = true;
// Icons were 80x80 plus 8px of padding; Icons start at 24x24 now so: 80 - 8 = 72, 72 / 24 = 3 therefore scale is 3x.
scale = 3;
padding = 8;
}
None => {}
}
2024-11-02 07:04:33 +00:00
// Rendering
let tree = match usvg::Tree::from_str(&icon_string, &usvg::Options::default()) {
Ok(tree) => tree,
Err(e) => {
error(&format!("Failed to load icon into tree: {}", e));
return Response::new(req, 500, Body::Static("Failed to load icon into tree"))
}
};
let length = if backwards_compatibility { 80 } else { 24 * scale + padding };
2024-11-02 00:56:25 +00:00
let mut pixmap = match Pixmap::new(length, length) {
Some(pixmap) => pixmap,
2024-11-02 06:15:42 +00:00
None => {
error("Failed to create pixmap");
return Response::new(req, 500, Body::Static("Failed to create pixmap"))
},
2024-11-02 00:56:25 +00:00
};
let mut pixmap_mut = pixmap.as_mut();
2024-11-02 01:34:16 +00:00
pixmap_mut.fill(background);
2024-11-02 00:56:25 +00:00
resvg::render(
&tree,
Transform::from_scale(scale as f32, scale as f32)
.post_concat(Transform::from_translate(length as f32 / 2.0 - 12.0 * scale as f32, length as f32 / 2.0 - 12.0 * scale as f32)),
&mut pixmap_mut,
);
let png = match pixmap.encode_png() {
Ok(png) => png,
2024-11-02 06:15:42 +00:00
Err(e) => {
error(&format!("Failed to encode PNG: {}", e));
return Response::new(req, 500, Body::Static("Failed to encode PNG"))
},
2024-11-02 00:56:25 +00:00
};
return Response::new(req, 200, Body::Bytes(png, "image/png"));
}
});
2024-10-30 02:49:27 +00:00
handlers
.bind(listener)
.unwrap_or_else(|_| fatal("Failed to bind handlers"));
2024-11-02 06:15:42 +00:00
println!("luciders started on port {}", port);
println!("Serving {} icons", icons_rc.icons_len())
2024-10-29 20:58:10 +00:00
}
fn fatal(msg: &'static str) -> ! {
eprintln!("[FATAL] {}", msg);
process::exit(1);
}
2024-11-02 06:15:42 +00:00
fn error(msg: &str) {
2024-10-29 20:58:10 +00:00
eprintln!("[ERROR] {}", msg);
}