minetest-tiler/mapserver/src/main.rs

204 lines
4.9 KiB
Rust

#![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<config::Config>,
) -> 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<Vec<u8>, 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<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
config: Arc<config::Config>,
) -> 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<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
config: Arc<config::Config>,
) -> Result<Vec<u8>, Error> {
generate_tile(z, x, y, tile_path, tasks, config)?;
read_tile_file(tile_path)
}
async fn req_tile(
req: Request<()>,
tasks: Arc<RwLock<HashMap<(i32, i32, i32), Sender<()>>>>,
config: Arc<config::Config>,
) -> 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()),
}
}
}
}