#![feature(proc_macro_hygiene, decl_macro)] extern crate serde; extern crate serde_json; #[macro_use] extern crate rocket; extern crate rocket_contrib; use rocket::response::Redirect; use rocket::{Rocket, State}; use rocket_contrib::serve::StaticFiles; use rocket_contrib::templates::Template; use rand::distributions::Uniform; use rand::Rng; use std::ops::Deref; use std::sync::Arc; use chgk_ledb_lib::db; use chgk_ledb_lib::questions::Question; use mini_moka::sync::Cache; use std::time::Duration; const DB_FILENAME: &str = "db.dat"; trait ErrorEmpty { type Output; fn err_empty(self) -> Result; } impl ErrorEmpty for Result { type Output = T; fn err_empty(self) -> Result { self.map_err(|_| ()) } } #[derive(Clone)] struct ArcTemplateData { value: Arc, } impl ArcTemplateData { fn new(value: serde_json::Value) -> ArcTemplateData { ArcTemplateData { value: Arc::new(value), } } fn render(&self, name: &'static str) -> Template { Template::render(name, self.value.deref()) } } type TemplateCache = mini_moka::sync::Cache; type DataBaseInner = db::Reader; type DataBase = Arc; struct AppState { db: DataBase, database_distribution: Uniform, } impl From for AppState { fn from(db: DataBaseInner) -> Self { let last_id = db.len(); let database_distribution = rand::distributions::Uniform::new_inclusive(1usize, last_id); let db = Arc::new(db); Self { db, database_distribution, } } } fn random_question_id(database_distribution: &Uniform) -> usize { let mut rng = rand::thread_rng(); rng.sample(database_distribution) } fn get_question(db: &DataBase, id: usize) -> Result { db.get(id - 1).err_empty() } fn show_question_details( template_name: &'static str, data: &AppState, cache: &TemplateCache, id: usize, ) -> Template { if let Some(value) = cache.get(&id) { return value.render(template_name); } match get_question(&data.db, id) { Ok(question) => { let mut context = serde_json::to_value(question).expect("question serialize"); if context.is_object() { let next_id = random_question_id(&data.database_distribution); context["next"] = serde_json::to_value(next_id).expect("question id serialize"); } let value = ArcTemplateData::new(context); let result = value.render(template_name); cache.insert(id, value); result } Err(_) => { use std::collections::HashMap; let context: HashMap = HashMap::new(); Template::render("404", context) } } } #[get("/q/")] fn show_question(data: State, cache: State, id: usize) -> Template { show_question_details("question", data.inner(), cache.inner(), id) } #[get("/q//a")] fn show_answer(data: State, cache: State, id: usize) -> Template { show_question_details("answer", data.inner(), cache.inner(), id) } #[get("/q/0")] fn question0() -> Redirect { Redirect::to("/") } #[get("/q/0/a")] fn answer0() -> Redirect { Redirect::to("/") } #[get("/")] fn index(data: State) -> Redirect { let id = random_question_id(&data.database_distribution); Redirect::temporary(format!("/q/{}", id)) } #[catch(404)] fn not_found(_req: &rocket::Request) -> Template { use std::collections::HashMap; let context: HashMap = HashMap::new(); Template::render("404", context) } fn rocket() -> Rocket { let state: AppState = db::Reader::new(DB_FILENAME, 2048).expect("open db").into(); let cache: TemplateCache = Cache::builder() .time_to_idle(Duration::from_secs(15 * 60)) .max_capacity(300) .build(); rocket::ignite() .manage(state) .manage(cache) .register(catchers![not_found]) .mount( "/", routes![index, show_question, show_answer, question0, answer0], ) .mount("/q", routes![index]) .mount("/q/static", StaticFiles::from("static/")) .attach(Template::fairing()) } fn main() { rocket().launch(); }