Use argument as listen address
[litoprism.git] / src / main.rs
1 use std::convert::From;
2 use std::env;
3 use std::io::Cursor;
4 use std::fmt::Display;
5 use std::fmt;
6 use std::borrow::BorrowMut;
7 use std::sync::{Arc, Mutex};
8 use num_traits::Zero;
9 use num_traits::cast::AsPrimitive;
10 use percent_encoding::percent_decode_str;
11 use actix_web::{get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
12 use actix_web::body::BoxBody;
13 use actix_web::http::StatusCode;
14 use image::{ImageBuffer, ColorType, Rgb, RgbImage, write_buffer_with_format};
15 use image::ImageOutputFormat::Png;
16 use image::imageops::{FilterType, overlay, resize};
17 use image::error::{LimitError, LimitErrorKind};
18 use actix_web::cookie::time::OffsetDateTime;
19
20
21 const TIMEAVATAR_SIZE_U32: u32 = 6;
22 const TIMEAVATAR_SIZE_I64: i64 = TIMEAVATAR_SIZE_U32 as i64;
23
24 #[derive(Debug)]
25 struct ImageError(image::ImageError);
26
27 impl ImageError {
28 fn dimension_error() -> ImageError {
29 ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError)))
30 }
31 }
32
33 impl Display for ImageError {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "{}", &self.0)
36 }
37 }
38
39 impl ResponseError for ImageError {
40 fn status_code(&self) -> StatusCode {
41 StatusCode::INTERNAL_SERVER_ERROR
42 }
43
44 fn error_response(&self) -> HttpResponse<BoxBody> {
45 HttpResponse::InternalServerError().body(format!("{}\n", &self))
46 }
47 }
48
49 #[derive(Debug)]
50 enum CanvasError {
51 NotExists,
52 DimensionMismatch((u32, u32)),
53 ImageError(ImageError)
54 }
55
56 impl Display for CanvasError {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "Canvas Error: see error_response() for more details")
59 }
60 }
61
62 impl ResponseError for CanvasError {
63 fn status_code(&self) -> StatusCode {
64 match &self {
65 CanvasError::NotExists => StatusCode::NOT_FOUND,
66 CanvasError::DimensionMismatch(_) => StatusCode::BAD_REQUEST,
67 CanvasError::ImageError(e) => e.status_code()
68 }
69 }
70
71 fn error_response(&self) -> HttpResponse<BoxBody> {
72 match &self {
73 CanvasError::NotExists => HttpResponse::NotFound().body("Canvas does not exist."),
74 CanvasError::DimensionMismatch(dim) =>
75 HttpResponse::BadRequest().body(format!("Supplied dimensions don't match the dimensions of existing canvas {:?}.", dim)),
76 CanvasError::ImageError(e) => e.error_response()
77 }
78 }
79 }
80
81 impl From<ImageError> for CanvasError {
82 fn from(e: ImageError) -> CanvasError {
83 CanvasError::ImageError(e)
84 }
85 }
86
87 struct ToRgbIter<'a, T> where T: AsPrimitive<u8> {
88 iter: &'a mut dyn Iterator<Item = T>
89 }
90
91 impl<'a, T: AsPrimitive<u8>> ToRgbIter<'a, T> {
92 fn new(iter: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
93 ToRgbIter { iter }
94 }
95 }
96
97 impl<T: AsPrimitive<u8> + Zero> Iterator for ToRgbIter<'_, T> {
98 type Item = Rgb<u8>;
99 fn next(&mut self) -> Option<Rgb<u8>> {
100 if let Some(r) = self.iter.next() {
101 let g = self.iter.next().unwrap_or_else(T::zero);
102 let b = self.iter.next().unwrap_or_else(T::zero);
103 Some(Rgb([r.as_(), g.as_(), b.as_()]))
104 }
105 else {
106 None
107 }
108 }
109 }
110
111 fn to_imageresult<T> (result: Result<T, image::ImageError>) -> Result<T, ImageError> {
112 match result {
113 Ok(v) => Ok(v),
114 Err(e) => Err(ImageError(e))
115 }
116 }
117
118 fn to_canvasresult<T: std::fmt::Debug> (result: Result<T, ImageError>) -> Result<T, CanvasError> {
119 match result {
120 Ok(v) => Ok(v),
121 Err(e) => Err(CanvasError::ImageError(e))
122 }
123 }
124
125 #[derive(Debug)]
126 struct Canvas {
127 pen: usize,
128 img: RgbImage
129 }
130
131 impl Canvas {
132 fn new(width: u32, height: u32) -> Canvas {
133 Canvas { pen: 0, img: ImageBuffer::new(width, height) }
134 }
135
136 fn from_image(img: RgbImage) -> Canvas {
137 Canvas { pen: 0, img }
138 }
139
140 /*fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
141 self.img = img;
142 self.pen = pen;
143 self
144 }*/
145
146 fn advance_pen(mut self, offset: usize) -> Canvas {
147 //Canvas { pen: self.pen + offset, img: self.img }
148 self.pen += offset;
149 self.pen %= self.img.width() as usize * self.img.height() as usize;
150 self
151 }
152 }
153
154 struct Canvas0(Option<Canvas>);
155 struct Canvas1(Option<Canvas>);
156 struct Canvas2(Option<Canvas>);
157
158 #[get("/hello/{name}")]
159 async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
160 println!("{:?}", req);
161 format!("Hello {name}!\n{:?}", req.headers().get("user-agent"))
162 }
163
164 fn rgb_encode_to_canvas(mut canvas: Canvas, data: &mut dyn Iterator<Item = u8>) -> Result<Canvas, ImageError> {
165 let mut pixels = canvas.img.pixels_mut().skip(canvas.pen);
166 let mut counter = 0;
167
168 for sp in ToRgbIter::new(data) {
169 let mut dp = match pixels.next() {
170 Some(pixel) => pixel,
171 None => {
172 pixels = canvas.img.pixels_mut().skip(0);
173 pixels.next().ok_or_else(ImageError::dimension_error)?
174 }
175 };
176 dp.0 = sp.0;
177 counter += 1;
178 }
179
180 Ok(canvas.advance_pen(counter))
181 }
182
183 fn rgb_encode(img: RgbImage, data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError> {
184 Ok(rgb_encode_to_canvas(Canvas::from_image(img), data)?.img)
185 }
186
187 fn apply_to_canvas<'a, F>(f: F, canvas_option: &'a mut Option<Canvas>, dimensions: Option<(u32, u32)>, data: &str) -> Result<&'a mut Canvas, CanvasError>
188 where F: Fn(Canvas, &mut dyn Iterator<Item = u8>) -> Result<Canvas, CanvasError>
189 {
190 let canvas = match canvas_option.take() {
191 Some(canvas) => match dimensions {
192 Some(dimensions) => {
193 if dimensions == canvas.img.dimensions() {
194 Ok(canvas)
195 }
196 else {
197 Err(CanvasError::DimensionMismatch(canvas_option.insert(canvas).img.dimensions()))
198 }
199 },
200 None =>
201 Ok(canvas)
202 },
203 None => match dimensions {
204 Some((dim_x, dim_y)) =>
205 Ok(Canvas::new(dim_x, dim_y)),
206 None =>
207 Err(CanvasError::NotExists)
208 }
209 }?;
210
211 Ok(canvas_option.insert(f(canvas, percent_decode_str(data).into_iter().borrow_mut())?))
212 }
213
214 fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
215 // Image must not be larger than 1 megapixel
216 if dim_x * dim_y * scale > 1048576 {
217 return Err(ImageError::dimension_error())
218 }
219
220 let img: RgbImage = rgb_encode(ImageBuffer::new(dim_x, dim_y), data)?;
221 let cursor = image_to_cursor(img, scale)?;
222 Ok(cursor)
223 }
224
225 fn image_to_cursor(img: RgbImage, scale: u32) -> Result<Cursor<Vec<u8>>, ImageError> {
226 let (dim_x, dim_y) = img.dimensions();
227 let (dim_x, dim_y) = (dim_x * scale, dim_y * scale);
228
229 // Image must not be larger than 1 megapixel
230 if dim_x * dim_y > 1048576 {
231 return Err(ImageError::dimension_error())
232 }
233
234 let mut cursor = Cursor::new(Vec::new());
235 let img = resize(&img, dim_x, dim_y, FilterType::Nearest);
236 to_imageresult(write_buffer_with_format(&mut cursor, &img, dim_x, dim_y, ColorType::Rgb8, Png))?;
237 Ok(cursor)
238 }
239
240
241 #[get("/gen/0/{data}")]
242 async fn img_gen0(req: HttpRequest) -> Result<impl Responder> {
243 let data = req.uri().path().split('/').nth(3).unwrap();
244 let cursor = make_png(32, 32, 16, percent_decode_str(data).into_iter().borrow_mut())?;
245 Ok(HttpResponse::build(StatusCode::OK)
246 .content_type("image/png")
247 .body(cursor.into_inner()))
248 }
249
250 #[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
251 async fn img_gen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
252 let (dim_x, dim_y, scale) = path.into_inner();
253 let data = req.uri().path().split('/').nth(6).unwrap();
254 let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(data).into_iter().borrow_mut())?;
255 Ok(HttpResponse::build(StatusCode::OK)
256 .content_type("image/png")
257 .body(cursor.into_inner()))
258 }
259
260 #[get("/gen/2/{dim_x}/{dim_y}/{scale}")]
261 async fn img_gen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
262 let (dim_x, dim_y, scale) = path.into_inner();
263 let data = match req.headers().get("user-agent") {
264 Some(header) => header.to_str().unwrap_or(""),
265 _ => ""
266 };
267 let cursor = make_png(dim_x, dim_y, scale, &mut data.bytes())?;
268 Ok(HttpResponse::build(StatusCode::OK)
269 .content_type("image/png")
270 .body(cursor.into_inner()))
271 }
272
273 #[get("/gen/3/{scale}")]
274 async fn img_gen3(req: HttpRequest, path: web::Path<u32>) -> Result<impl Responder> {
275 let scale = path.into_inner();
276 let data = match req.headers().get("user-agent") {
277 Some(header) => header.to_str().unwrap_or(""),
278 _ => ""
279 };
280 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
281 let mut resimg: RgbImage = ImageBuffer::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32);
282 let time = OffsetDateTime::now_utc().time();
283 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
284 overlay(&mut resimg, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
285 let cursor = image_to_cursor(resimg, scale)?;
286 Ok(HttpResponse::build(StatusCode::OK)
287 .content_type("image/png")
288 .body(cursor.into_inner()))
289 }
290
291 #[get("/pgen/0/{data}")]
292 async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Canvas0>>>) -> Result<impl Responder> {
293 let data = req.uri().path().split('/').nth(3).unwrap();
294 let scale = 16;
295 let img: RgbImage = ({
296 let canvas_option = &mut *canvas0.lock().unwrap();
297 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, None, data)?.img.clone())
298 } as Result<RgbImage, CanvasError>)?;
299 let cursor = image_to_cursor(img, scale)?;
300 Ok(HttpResponse::build(StatusCode::OK)
301 .content_type("image/png")
302 .body(cursor.into_inner()))
303 }
304
305 #[get("/pgen/1/{dim_x}/{dim_y}/{scale}/{data}")]
306 async fn img_pgen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
307 let (dim_x, dim_y, scale) = path.into_inner();
308 let data = req.uri().path().split('/').nth(6).unwrap();
309 let img: RgbImage = ({
310 let canvas_option = &mut *canvas1.lock().unwrap();
311 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
312 } as Result<RgbImage, CanvasError>)?;
313 let cursor = image_to_cursor(img, scale)?;
314 Ok(HttpResponse::build(StatusCode::OK)
315 .content_type("image/png")
316 .body(cursor.into_inner()))
317 }
318
319 #[get("/pgen/2/{dim_x}/{dim_y}/{scale}")]
320 async fn img_pgen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
321 let (dim_x, dim_y, scale) = path.into_inner();
322 let data = match req.headers().get("user-agent") {
323 Some(header) => header.to_str().unwrap_or(""),
324 _ => ""
325 };
326 let img: RgbImage = ({
327 let canvas_option = &mut *canvas1.lock().unwrap();
328 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
329 } as Result<RgbImage, CanvasError>)?;
330 let cursor = image_to_cursor(img, scale)?;
331 Ok(HttpResponse::build(StatusCode::OK)
332 .content_type("image/png")
333 .body(cursor.into_inner()))
334 }
335
336 #[get("/pgen/3/{scale}")]
337 async fn img_pgen3(req: HttpRequest, path: web::Path<u32>, canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
338 let scale = path.into_inner();
339 let data = match req.headers().get("user-agent") {
340 Some(header) => header.to_str().unwrap_or(""),
341 _ => ""
342 };
343 let time = OffsetDateTime::now_utc().time();
344 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
345 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
346 let resimg: RgbImage = ({
347 let canvas_option = &mut *canvas2.lock().unwrap();
348 let mut canvas = match canvas_option.0.take() {
349 Some(canvas) => canvas,
350 None => Canvas::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32)
351 };
352 overlay(&mut canvas.img, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
353 Ok(canvas_option.0.insert(canvas).img.clone())
354 } as Result<RgbImage, CanvasError>)?;
355 let cursor = image_to_cursor(resimg, scale)?;
356 Ok(HttpResponse::build(StatusCode::OK)
357 .content_type("image/png")
358 .body(cursor.into_inner()))
359 }
360
361 #[get("/pdrop/{canvas_id}")]
362 async fn pdrop(canvas_id: web::Path<u8>,
363 canvas0: web::Data<Arc<Mutex<Canvas0>>>,
364 canvas1: web::Data<Arc<Mutex<Canvas1>>>,
365 canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
366
367 let canvas_id = canvas_id.into_inner();
368 match canvas_id {
369 0 => canvas0.lock().unwrap().0.take(),
370 1 => canvas1.lock().unwrap().0.take(),
371 2 => canvas2.lock().unwrap().0.take(),
372 _ => None
373 }.ok_or(CanvasError::NotExists)?;
374 Ok(HttpResponse::build(StatusCode::OK)
375 .body(format!("Canvas {} dropped.", canvas_id)))
376 }
377
378
379 #[actix_web::main] // or #[tokio::main]
380 async fn main() -> std::io::Result<()> {
381 env_logger::init();
382 let listenaddress = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string());
383 let canvas0 = Arc::new(Mutex::new(Canvas0(Some(Canvas::new(32, 32)))));
384 let canvas1 = Arc::new(Mutex::new(Canvas1(None)));
385 let canvas2 = Arc::new(Mutex::new(Canvas2(None)));
386 HttpServer::new(move || {
387 App::new()
388 .service(greet)
389 .service(img_gen0)
390 .service(img_gen1)
391 .service(img_gen2)
392 .service(img_gen3)
393 .service(img_pgen0)
394 .service(img_pgen1)
395 .service(img_pgen2)
396 .service(img_pgen3)
397 .service(pdrop)
398 .app_data(web::Data::new(canvas0.clone()))
399 .app_data(web::Data::new(canvas1.clone()))
400 .app_data(web::Data::new(canvas2.clone()))
401 })
402 .bind(listenaddress)?
403 .run()
404 .await
405 }