// Copyright © 2020 Jakob L. Kreuze // // This file is part of бирка-тян. // // бирка-тян is free software; you can redistribute it and/or modify it // under the terms of the GNU Affero General Public License as published // by the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // бирка-тян is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public // License along with бирка-тян. If not, see . #![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate anyhow; extern crate image; #[macro_use] extern crate rocket; extern crate rocket_contrib; #[macro_use] extern crate serde_derive; mod database; use anyhow::Result; use database::{hash_file, Query, TagDatabase}; use image::ImageFormat; use rocket::{Data, State}; use rocket_contrib::json::Json; use std::env; use std::fs; use std::fs::File; use std::os::unix::fs::symlink; use std::path::Path; use std::sync::Mutex; type SiteState<'a> = State<'a, Mutex>; struct DatabaseConnection { tb: TagDatabase, image_dir: String, } impl DatabaseConnection { fn new() -> Result { let bin = env::current_exe()?.canonicalize()?; let bin_dir = bin.parent().unwrap(); let image_dir = String::from(bin_dir.join("image/").to_str().unwrap()); let tb_path = bin_dir.join("birka.db"); let tb = TagDatabase::new(tb_path.to_str().unwrap())?; Ok(DatabaseConnection { tb, image_dir }) } } #[get("/")] fn index(tb: SiteState) -> &'static str { "Hello, world!" } #[derive(Serialize)] struct ImageResult { filename: String, tags: Vec, } #[get("/posts?")] fn get_posts(conn: SiteState, tags: String) -> Json> { let tb = &conn.inner().lock().unwrap().tb; let tags = tags.split(",").collect::>(); let results = tb .query(Query { tags: &tags[..], last_id: Some(0), limit: Some(50), }) .unwrap(); Json( results .iter() .map(|image| ImageResult { filename: image.filename.clone(), tags: tb.tags_for_image(image.id).unwrap(), }) .collect(), ) } #[post("/posts/", data = "")] fn put_posts(conn: SiteState, filename: String, data: Data) -> Json<&str> { let conn = conn.inner().lock().unwrap(); let path = Path::new(&conn.image_dir).join(filename); data.stream_to_file(&path).unwrap(); conn.tb .import_image(&path, &hash_file(&path).unwrap()) .unwrap(); Json("uploaded!") } /// Maybe initialize the directory for storing image symlinks and thumbnails. fn create_image_directory(conn: &DatabaseConnection) { fs::create_dir(&conn.image_dir).ok(); for image in conn.tb.images_by_tags(&vec![][..]).unwrap() { let src = format!("{}/{}", image.orig_dir, image.filename); let dst = format!("{}/{}", conn.image_dir, image.filename); symlink(&src, &dst).ok(); let stem = Path::new(&dst).file_stem().unwrap().to_str().unwrap(); let thumb_path = format!("{}_thumb.png", stem); let thumb_path = Path::new(&thumb_path); if !thumb_path.exists() { let im = image::open(&Path::new(&src)).unwrap(); let fout = &mut File::create(&thumb_path).unwrap(); im.thumbnail(100, 100) .write_to(fout, ImageFormat::Png) .unwrap(); } } } fn main() { let conn = DatabaseConnection::new().unwrap(); create_image_directory(&conn); rocket::ignite() .manage(Mutex::new(conn)) .mount("/", routes![index, get_posts]) .launch(); }