diff --git a/Cargo.lock b/Cargo.lock index 5ad003e..7ca613f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -45,7 +45,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -66,6 +66,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -93,7 +99,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -121,13 +127,25 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "filetime" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + [[package]] name = "flate2" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crc32fast", "libc", "miniz_oxide", @@ -139,12 +157,47 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "futures-channel" version = "0.3.16" @@ -191,7 +244,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -319,13 +372,42 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", ] [[package]] @@ -334,12 +416,28 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.99" @@ -371,7 +469,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -384,6 +482,7 @@ dependencies = [ "lazy_static", "libsqlite3-sys", "log", + "notify", "pretty_env_logger", "r2d2", "r2d2_sqlite", @@ -411,6 +510,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + [[package]] name = "mio" version = "0.7.13" @@ -419,9 +537,33 @@ checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", "log", - "miow", + "miow 0.3.7", "ntapi", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", ] [[package]] @@ -430,7 +572,36 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "notify" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", + "winapi 0.3.9", ] [[package]] @@ -439,7 +610,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -475,12 +646,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -566,7 +737,7 @@ dependencies = [ "libc", "rand_core 0.3.1", "rdrand", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -625,7 +796,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -649,6 +820,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scheduled-thread-pool" version = "0.2.5" @@ -723,7 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -781,14 +961,14 @@ dependencies = [ "bytes", "libc", "memchr", - "mio", + "mio 0.7.13", "num_cpus", "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", "tokio-macros", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -828,7 +1008,7 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "pin-project-lite", "tracing-core", ] @@ -878,6 +1058,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -894,6 +1085,12 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -904,6 +1101,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -916,7 +1119,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -924,3 +1127,13 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] diff --git a/Cargo.toml b/Cargo.toml index 322a5a3..a65432f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ rusqlite = "0.25" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.6", features = ["full"] } +notify = "4.0.17" [dev-dependencies] tempdir = "0.3" diff --git a/README.md b/README.md index b2feab7..8811ab7 100644 --- a/README.md +++ b/README.md @@ -21,27 +21,22 @@ USAGE: mbtileserver [FLAGS] [OPTIONS] FLAGS: - --disable-preview - Disable preview map - -h, --help - Prints help information - - -V, --version - Prints version information - + --allow-reload-api Allow reloading tilesets with /reload endpoint + --allow-reload-signal Allow reloading tilesets with a SIGHUP + --disable-preview Disable preview map + --disable-watcher Disable fs watcher for automatic tileset reload + -h, --help Prints help information + -V, --version Prints version information OPTIONS: - --allowed-hosts - "*" matches all domains and "." matches all subdomains for the given domain - [default: localhost, 127.0.0.1, [::1]] - -d, --directory - Tiles directory - [default: ./tiles] - -H, --header
... - Add custom header - -p, --port - Server port - [default: 3000] + --allowed-hosts A comma-separated list of allowed hosts [default: localhost, 127.0.0.1, + [::1]] + -d, --directory Tiles directory + [default: ./tiles] + -H, --header
... Add custom header + -p, --port Server port + [default: 3000] + --reload-interval An interval at which tilesets get reloaded ``` Run `mbtileserver` to start serving the mbtiles in a given folder. The default folder is `./tiles` and you can change it with `-d` flag. @@ -53,6 +48,7 @@ You can adjust the log level by setting `RUST_LOG` environment variable. Possbil | Endpoint | Description | |--------------------------------------------------------------|--------------------------------------------------------------------------------| +| /reload | reloads tilesets from directory (if enabled with `--allow-reload`) | | /services | lists all discovered and valid mbtiles in the tiles directory | | /services/\ | shows tileset metadata | | /services/\/map | tileset preview | diff --git a/src/config.rs b/src/config.rs index 5195307..f917024 100755 --- a/src/config.rs +++ b/src/config.rs @@ -1,19 +1,28 @@ -use std::collections::HashMap; use std::env; use std::path::PathBuf; +use std::time::Duration; use clap::{crate_version, App, Arg, ArgMatches}; +use regex::Regex; use crate::errors::{Error, Result}; use crate::tiles; +lazy_static! { + static ref DURATION_RE: Regex = Regex::new(r"\d+[smhd]").unwrap(); +} + #[derive(Clone, Debug)] pub struct Args { - pub tilesets: HashMap, + pub tilesets: tiles::Tilesets, pub port: u16, pub allowed_hosts: Vec, pub headers: Vec<(String, String)>, pub disable_preview: bool, + pub allow_reload_api: bool, + pub allow_reload_signal: bool, + pub reload_interval: Option, + pub disable_watcher: bool, } pub fn get_app<'a, 'b>() -> App<'a, 'b> { @@ -57,6 +66,28 @@ pub fn get_app<'a, 'b>() -> App<'a, 'b> { .long("disable-preview") .help("Disable preview map\n"), ) + .arg( + Arg::with_name("allow_reload_api") + .long("allow-reload-api") + .help("Allow reloading tilesets with /reload endpoint\n"), + ) + .arg( + Arg::with_name("allow_reload_signal") + .long("allow-reload-signal") + .help("Allow reloading tilesets with a SIGHUP\n"), + ) + .arg( + Arg::with_name("reload_interval") + .long("reload-interval") + .help("An interval at which tilesets get reloaded") + .long_help("\"*\" in 1h30m format\n") + .takes_value(true), + ) + .arg( + Arg::with_name("disable_watcher") + .long("disable-watcher") + .help("Disable fs watcher for automatic tileset reload\n"), + ) } pub fn parse(matches: ArgMatches) -> Result { @@ -107,7 +138,35 @@ pub fn parse(matches: ArgMatches) -> Result { } } + let reload_interval = match matches.value_of("reload_interval") { + Some(str) => { + let mut duration = Duration::ZERO; + for mat in DURATION_RE.find_iter(str) { + let mut mat = mat.as_str().to_owned(); + let char = mat.chars().nth(mat.len() - 1).unwrap(); + mat.truncate(mat.len() - 1); + let multiplier = match char { + 's' => 1, + 'm' => 60, + 'h' => 60 * 60, + 'd' => 60 * 60 * 24, + _ => return Err(Error::Config("Invalid value for duration".to_string())), + }; + let qty = match mat.parse::() { + Ok(v) => v, + Err(_) => return Err(Error::Config("Invalid value for duration".to_string())), + }; + duration += Duration::from_secs(multiplier * qty); + } + Some(duration) + } + None => None, + }; + let disable_preview = matches.occurrences_of("disable_preview") != 0; + let allow_reload_api = matches.occurrences_of("allow_reload_api") != 0; + let allow_reload_signal = matches.occurrences_of("allow_reload_signal") != 0; + let disable_watcher = matches.occurrences_of("disable_watcher") != 0; Ok(Args { tilesets, @@ -115,6 +174,10 @@ pub fn parse(matches: ArgMatches) -> Result { allowed_hosts, headers, disable_preview, + allow_reload_api, + allow_reload_signal, + reload_interval, + disable_watcher, }) } diff --git a/src/main.rs b/src/main.rs index 04fa43d..2766414 100755 --- a/src/main.rs +++ b/src/main.rs @@ -30,13 +30,7 @@ fn main() { } }; - if let Err(e) = server::run( - args.port, - args.allowed_hosts, - args.headers, - args.disable_preview, - args.tilesets, - ) { + if let Err(e) = server::run(args) { error!("Server error: {}", e); std::process::exit(1); } diff --git a/src/server.rs b/src/server.rs index 41def0c..e3ce3fc 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,37 +1,85 @@ +use crate::config::Args; use hyper::service::{make_service_fn, service_fn}; use hyper::Server; -use std::collections::HashMap; +use notify::Watcher; +use std::time::Duration; use crate::service; -use crate::tiles::TileMeta; #[tokio::main] -pub async fn run( - port: u16, - allowed_hosts: Vec, - headers: Vec<(String, String)>, - disable_preview: bool, - tilesets: HashMap, -) -> Result<(), Box> { - let addr = ([0, 0, 0, 0], port).into(); +pub async fn run(args: Args) -> Result<(), Box> { + let addr = ([0, 0, 0, 0], args.port).into(); let server = Server::try_bind(&addr)?; - let service = make_service_fn(move |_conn| { - let tilesets = tilesets.clone(); - let allowed_hosts = allowed_hosts.clone(); - let headers = headers.clone(); - async move { - Ok::<_, hyper::Error>(service_fn(move |req| { - service::get_service( - req, - tilesets.clone(), - allowed_hosts.clone(), - headers.clone(), - disable_preview, - ) - })) - } - }); + let service = { + let args = args.clone(); + make_service_fn(move |_conn| { + let tilesets = args.tilesets.clone(); + let allowed_hosts = args.allowed_hosts.clone(); + let headers = args.headers.clone(); + let disable_preview = args.disable_preview; + let allow_reload_api = args.allow_reload_api; + async move { + Ok::<_, hyper::Error>(service_fn(move |req| { + service::get_service( + req, + tilesets.clone(), + allowed_hosts.clone(), + headers.clone(), + disable_preview, + allow_reload_api, + ) + })) + } + }) + }; + + if args.allow_reload_signal { + let tilesets = args.tilesets.clone(); + println!("Reloading on SIGHUP"); + tokio::spawn(async move { + let mut handler = + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::hangup()).unwrap(); + loop { + handler.recv().await; + println!("Caught SIGHUP, reloading tilesets"); + tilesets.reload(); + } + }); + } + + if let Some(interval) = args.reload_interval { + let tilesets = args.tilesets.clone(); + println!("Reloading every {} seconds", interval.as_secs()); + tokio::spawn(async move { + let mut timer = tokio::time::interval(interval); + loop { + timer.tick().await; + tilesets.reload(); + } + }); + } + + if !args.disable_watcher { + let tilesets = args.tilesets.clone(); + println!("Watching FS for changes on {:?}", tilesets.get_path()); + drop(std::thread::spawn(move || { + let (tx, rx) = std::sync::mpsc::channel(); + let mut watcher = notify::watcher(tx, Duration::from_secs(10)).unwrap(); + watcher + .watch(&args.tilesets.get_path(), notify::RecursiveMode::Recursive) + .unwrap(); + loop { + match rx.recv() { + Ok(_) => { + println!("Tileset directory changed, reloading"); + tilesets.reload() + } + Err(e) => println!("watch error: {:?}", e), + } + } + })); + } println!("Listening on http://{}", addr); server.serve(service).await?; diff --git a/src/service.rs b/src/service.rs index b0eb5e9..ceb307e 100755 --- a/src/service.rs +++ b/src/service.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use hyper::header::{CONTENT_ENCODING, CONTENT_TYPE, HOST}; use hyper::{Body, Request, Response, StatusCode}; @@ -8,7 +6,7 @@ use regex::Regex; use serde_json::json; use crate::errors::Result; -use crate::tiles::{get_grid_data, get_tile_data, TileMeta, TileSummaryJSON}; +use crate::tiles::{get_grid_data, get_tile_data, TileSummaryJSON, Tilesets}; use crate::utils::{encode, get_blank_image, DataFormat}; lazy_static! { @@ -103,10 +101,11 @@ fn is_host_valid(host: &Option<&str>, allowed_hosts: &[String]) -> bool { pub async fn get_service( request: Request, - tilesets: HashMap, + tilesets: Tilesets, allowed_hosts: Vec, headers: Vec<(String, String)>, disable_preview: bool, + allow_reload_api: bool, ) -> Result> { let host = get_host(&request); @@ -207,7 +206,7 @@ pub async fn get_service( // Tileset details (/services/) let tile_name = segments[1..].join("/"); let tile_meta = match tilesets.get(&tile_name) { - Some(tile_meta) => tile_meta.clone(), + Some(tile_meta) => tile_meta, None => { if segments[segments.len() - 1] == "map" { // Tileset map preview (/services//map) @@ -279,6 +278,13 @@ pub async fn get_service( .header(CONTENT_TYPE, "application/json") .body(Body::from(serde_json::to_string(&tile_meta_json).unwrap())) .unwrap()); // TODO handle error + } else if path == "/reload" { + if allow_reload_api { + tilesets.reload(); + return Ok(no_content()); + } else { + return Ok(forbidden()); + } } } }; @@ -301,6 +307,7 @@ mod tests { allowed_hosts: Option>, headers: Option>, disable_preview: bool, + allow_reload: bool, ) -> Response { let request = Request::builder() .uri(format!("{}{}", host, path)) @@ -314,6 +321,7 @@ mod tests { allowed_hosts.unwrap_or(vec![String::from("*")]), headers.unwrap_or(vec![]), disable_preview, + allow_reload, ) .await .unwrap() @@ -321,7 +329,7 @@ mod tests { #[tokio::test] async fn get_services() { - let response = setup("http://localhost", "/services", None, None, false).await; + let response = setup("http://localhost", "/services", None, None, false, false).await; assert_eq!(response.status(), 200); } @@ -333,6 +341,7 @@ mod tests { Some(vec![String::from("example.com")]), None, false, + false, ) .await; assert_eq!(response.status(), 403); @@ -346,6 +355,7 @@ mod tests { Some(vec![String::from("*")]), None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -359,6 +369,7 @@ mod tests { Some(vec![String::from("example.com")]), None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -372,6 +383,7 @@ mod tests { Some(vec![String::from("example.com")]), None, false, + false, ) .await; assert_eq!(response.status(), 403); @@ -385,6 +397,7 @@ mod tests { Some(vec![String::from(".example.com")]), None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -398,6 +411,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -411,6 +425,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -424,6 +439,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -438,6 +454,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -455,6 +472,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 200); @@ -480,6 +498,7 @@ mod tests { None, None, false, + false, ) .await; assert_eq!(response.status(), 204); @@ -493,6 +512,7 @@ mod tests { None, None, true, + false, ) .await; assert_eq!(response.status(), 404); diff --git a/src/tiles.rs b/src/tiles.rs index e901b31..837acbb 100755 --- a/src/tiles.rs +++ b/src/tiles.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::fs::read_dir; use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; use r2d2_sqlite::SqliteConnectionManager; use rusqlite::{params, OpenFlags}; @@ -59,6 +60,55 @@ pub struct UTFGrid { pub keys: Vec, } +#[derive(Clone, Debug)] +struct TilesetsData { + pub data: HashMap, + pub path: PathBuf, +} + +#[derive(Clone, Debug)] +pub struct Tilesets { + data: Arc>, +} +impl Tilesets { + pub fn new(data: HashMap, path: PathBuf) -> Tilesets { + Tilesets { + data: Arc::new(Mutex::new(TilesetsData { data, path })), + } + } + + pub fn get>(&self, key: S) -> Option { + self.data.lock().unwrap().data.get(key.as_ref()).cloned() + } + #[allow(dead_code)] + pub fn len(&self) -> usize { + self.data.lock().unwrap().data.len() + } + #[allow(dead_code)] + pub fn contains_key>(&self, key: S) -> bool { + self.data.lock().unwrap().data.contains_key(key.as_ref()) + } + + pub fn get_path(&self) -> PathBuf { + self.data.lock().unwrap().path.clone() + } + + pub fn reload(&self) { + let mut data = self.data.lock().unwrap(); + let replacement = discover_tilesets(String::new(), data.path.clone()); + data.data.clear(); + data.data.extend(replacement); + } +} +impl IntoIterator for Tilesets { + type Item = (String, TileMeta); + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.data.lock().unwrap().data.clone().into_iter() + } +} + pub fn get_data_format_via_query( tile_name: &str, connection: &Connection, @@ -172,10 +222,10 @@ pub fn get_tile_details(path: &Path, tile_name: &str) -> Result { Ok(metadata) } -pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> HashMap { +pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> Tilesets { // Walk through the given path and its subfolders, find all valid mbtiles and create and return a map of mbtiles file names to their absolute path let mut tiles = HashMap::new(); - for p in read_dir(path).unwrap() { + for p in read_dir(path.clone()).unwrap() { let p = p.unwrap().path(); if p.is_dir() { let dir_name = p.file_stem().unwrap().to_str().unwrap(); @@ -196,7 +246,7 @@ pub fn discover_tilesets(parent_dir: String, path: PathBuf) -> HashMap Option {