| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- use std::error::Error;
- use std::fs::File;
- use std::io::{Read, Write};
- use std::path::{Path, PathBuf};
- use tile_source::TileSource;
- use toml::Value;
- use xdg;
-
- static DEFAULT_CONFIG: &'static str = include_str!("../default_config.toml");
-
-
- #[derive(Debug)]
- pub struct Config {
- tile_cache_dir: PathBuf,
- sources: Vec<(String, TileSource)>,
- fps: f64,
- }
-
- impl Config {
- pub fn load() -> Result<Config, String> {
- if let Ok(xdg_dirs) = xdg::BaseDirectories::with_prefix("deltamap") {
- if let Some(config_path) = xdg_dirs.find_config_file("config.toml") {
- info!("load config from path {:?}", config_path);
-
- Config::from_toml_file(config_path)
- } else {
- // try to write a default config file
- if let Ok(path) = xdg_dirs.place_config_file("config.toml") {
- if let Ok(mut file) = File::create(&path) {
- if file.write_all(DEFAULT_CONFIG.as_bytes()).is_ok() {
- info!("write default config to {:?}", &path);
- }
- }
- }
-
- Config::from_toml_str(DEFAULT_CONFIG)
- }
- } else {
- info!("load default config");
- Config::from_toml_str(DEFAULT_CONFIG)
- }
- }
-
- /// Returns a tile cache directory path at a standard XDG cache location. The returned path may
- /// not exist.
- fn default_tile_cache_dir() -> Result<PathBuf, String> {
- let xdg_dirs = xdg::BaseDirectories::with_prefix("deltamap")
- .map_err(|e| e.description().to_string())?;
-
- match xdg_dirs.find_cache_file("tiles") {
- Some(dir) => Ok(dir),
- None => Ok(xdg_dirs.get_cache_home().join("tiles")),
- }
- }
-
- pub fn from_toml_str(toml_str: &str) -> Result<Config, String> {
- match toml_str.parse::<Value>() {
- Ok(Value::Table(ref table)) => {
- let tile_cache_dir = {
- match table.get("tile_cache_dir") {
- Some(dir) => {
- PathBuf::from(
- dir.as_str()
- .ok_or_else(|| "tile_cache_dir has to be a string".to_string())?
- )
- },
- None => Config::default_tile_cache_dir()?,
- }
- };
-
- let fps = {
- match table.get("fps") {
- Some(&Value::Float(fps)) => fps,
- Some(&Value::Integer(fps)) => fps as f64,
- Some(_) => return Err("fps has to be an integer or a float.".to_string()),
- None => 60.0,
- }
- };
-
- let sources_table = table.get("tile_sources")
- .ok_or_else(|| "missing \"tile_sources\" table".to_string())?
- .as_table()
- .ok_or_else(|| "\"tile_sources\" has to be a table".to_string())?;
-
- let mut sources_vec: Vec<(String, TileSource)> = Vec::with_capacity(sources_table.len());
-
- for (id, (name, source)) in sources_table.iter().enumerate() {
- let min_zoom = source.get("min_zoom")
- .unwrap_or_else(|| &Value::Integer(0))
- .as_integer()
- .ok_or_else(|| "min_zoom has to be an integer".to_string())
- .and_then(|m| {
- if m < 0 || m > 30 {
- Err(format!("min_zoom = {} is out of bounds, has to be in interval [0, 30]", m))
- } else {
- Ok(m)
- }
- })?;
-
- let max_zoom = source.get("max_zoom")
- .ok_or_else(|| format!("source {:?} is missing \"max_zoom\" entry", name))?
- .as_integer()
- .ok_or_else(|| "max_zoom has to be an integer".to_string())
- .and_then(|m| {
- if m < 0 || m > 30 {
- Err(format!("max_zoom = {} is out of bounds, has to be in interval [0, 30]", m))
- } else {
- Ok(m)
- }
- })?;
-
- if min_zoom > max_zoom {
- warn!("min_zoom ({}) and max_zoom ({}) allow no valid tiles", min_zoom, max_zoom);
- } else if min_zoom == max_zoom {
- warn!("min_zoom ({}) and max_zoom ({}) allow only one zoom level", min_zoom, max_zoom);
- }
-
- let url_template = source.get("url_template")
- .ok_or_else(|| format!("source {:?} is missing \"url_template\" entry", name))?
- .as_str()
- .ok_or_else(|| "url_template has to be a string".to_string())?;
-
- let extension = source.get("extension")
- .ok_or_else(|| format!("source {:?} is missing \"extension\" entry", name))?
- .as_str()
- .ok_or_else(|| "extension has to be a string".to_string())?;
-
- if name.contains('/') || name.contains('\\') {
- return Err(format!("source name ({:?}) must not contain slashes (\"/\" or \"\\\")", name));
- }
-
- let mut path = PathBuf::from(&tile_cache_dir);
- path.push(name);
-
- sources_vec.push((
- name.clone(),
- TileSource::new(
- id as u32,
- url_template.to_string(),
- path,
- extension.to_string(),
- min_zoom as u32,
- max_zoom as u32,
- ),
- ));
- }
-
- Ok(
- Config {
- tile_cache_dir: tile_cache_dir,
- sources: sources_vec,
- fps: fps,
- }
- )
- },
- Ok(_) => Err("TOML file has invalid structure. Expected a Table as the top-level element.".to_string()),
- Err(e) => Err(e.description().to_string()),
- }
- }
-
- pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Config, String> {
- let mut file = File::open(path).map_err(|e| e.description().to_string())?;
-
- let mut content = String::new();
- file.read_to_string(&mut content).map_err(|e| e.description().to_string())?;
-
- Config::from_toml_str(&content)
- }
-
- pub fn tile_sources(&self) -> &[(String, TileSource)] {
- &self.sources
- }
-
- pub fn fps(&self) -> f64 {
- self.fps
- }
- }
-
- #[cfg(test)]
- mod tests {
- use config::*;
-
- #[test]
- fn default_config() {
- assert!(Config::from_toml_str(DEFAULT_CONFIG).is_ok())
- }
- }
|