#![feature(try_blocks)] mod config; use crossbeam_channel::{Receiver, Sender}; use log::{debug, error, info, warn}; use parking_lot::RwLock; use std::{collections::HashMap, io::Read, sync::Arc, time::SystemTime}; use tide::Request; #[derive(Debug)] enum Error { Io(std::io::Error), Minetestmapper, Todo, } #[async_std::main] async fn main() -> tide::Result<()> { simplelog::TermLogger::init( simplelog::LevelFilter::Info, simplelog::Config::default(), simplelog::TerminalMode::Mixed, simplelog::ColorChoice::Auto, ) .unwrap(); let config = config::Config::default(); assert!( config.base_url.starts_with('/') && config.base_url.ends_with('/'), "`base_url` must start and end with `/`" ); let config = Arc::new(config); let tasks = Arc::new(RwLock::new(HashMap::new())); let mut app = tide::new(); app.at(&format!("{}:z/:x/:y", config.base_url)).get({ let config = config.clone(); let tasks = tasks.clone(); move |req| req_tile(req, tasks.clone(), config.clone()) }); app.listen(&config.listen).await?; Ok(()) } fn run_minetestmapper( z: i32, x: i32, y: i32, tile_path: &str, config: Arc, ) -> Result<(), Error> { debug!("Generating tile ({},{},{})", z, x, y); std::process::Command::new(&config.minetestmapper_path) .args([ "-i", &config.world_path, "-o", tile_path, "--geometry", &format!( "{}:{}+{}+{}", x * config.tile_size as i32, -1 - z * config.tile_size as i32, config.tile_size, config.tile_size ), ]) .args(&config.minetestmapper_args) .output() .map_err(|e| { error!("Generating tile ({},{},{}): {}", z, x, y, e); Error::Minetestmapper })?; Ok(()) } fn read_tile_file(tile_path: &str) -> Result, Error> { let mut tile_content = Vec::new(); std::fs::File::open(&tile_path) .map_err(Error::Io)? .read(&mut tile_content) .map_err(Error::Io)?; // TODO resp_tile if error Ok(tile_content) } fn generate_tile( z: i32, x: i32, y: i32, tile_path: &str, tasks: Arc>>>, config: Arc, ) -> Result<(), Error> { let mut generate = true; loop { let mut tasks_guard = tasks.write(); if let Some(sender) = tasks_guard.get(&(z, x, y)) { let sender = sender.clone(); drop(tasks_guard); generate = false; sender.send(()).ok(); } else { if generate { let (sender, receiver) = crossbeam_channel::bounded(0); tasks_guard.insert((z, x, y), sender); drop(tasks_guard); let res: Result<(), Error> = try { if z == config.zoom_default { run_minetestmapper(z, x, y, tile_path, config)?; } else { Err(Error::Todo)?; } }; tasks.write().remove(&(z, x, y)); receiver.recv().ok(); res?; } else { drop(tasks_guard); } return Ok(()); } } } fn resp_tile( z: i32, x: i32, y: i32, tile_path: &str, tasks: Arc>>>, config: Arc, ) -> Result, Error> { generate_tile(z, x, y, tile_path, tasks, config)?; read_tile_file(tile_path) } async fn req_tile( req: Request<()>, tasks: Arc>>>, config: Arc, ) -> tide::Result { let z: i32 = req.param("z")?.parse()?; let x: i32 = req.param("x")?.parse()?; let y: i32 = req.param("y")?.parse()?; let tile_path = format!("{}/{}/{}/{}.png", config.output_path, z, x, y); match std::fs::metadata(&tile_path) { Ok(metadata) => match metadata.modified() { Ok(modified_time) => { if SystemTime::now() .duration_since(modified_time) .unwrap() .as_secs() > config.cache_age { match resp_tile(z, x, y, &tile_path, tasks, config) { Ok(tile_content) => Ok(tide::Response::builder(200) .content_type(http_types::mime::PNG) .body(tile_content) .build()), Err(_) => Ok(tide::StatusCode::InternalServerError.into()), } } else { match read_tile_file(&tile_path) { Ok(tile_content) => Ok(tide::Response::builder(200) .content_type(http_types::mime::PNG) .body(tile_content) .build()), Err(_) => Ok(tide::StatusCode::InternalServerError.into()), } } } Err(e) => { warn!("Cannot get modified time of `{}`: {}", tile_path, e); match resp_tile(z, x, y, &tile_path, tasks, config) { Ok(tile_content) => Ok(tide::Response::builder(200) .content_type(http_types::mime::PNG) .body(tile_content) .build()), Err(_) => Ok(tide::StatusCode::InternalServerError.into()), } } }, Err(e) => { if e.kind() == std::io::ErrorKind::NotFound { debug!("Tile not found `{}`", tile_path); } else { warn!("Cannot get metadata of `{}`: {}", tile_path, e); } match resp_tile(z, x, y, &tile_path, tasks, config) { Ok(tile_content) => Ok(tide::Response::builder(200) .content_type(http_types::mime::PNG) .body(tile_content) .build()), Err(_) => Ok(tide::StatusCode::InternalServerError.into()), } } } }