| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- use clap;
- use directories::ProjectDirs;
- use std::fmt::Debug;
- use std::fs::File;
- use std::io::{Read, Write};
- use std::path::{Path, PathBuf};
- use tile_source::TileSource;
- use toml::Value;
-
- static DEFAULT_CONFIG: &'static str = "";
- static DEFAULT_TILE_SOURCES: &'static str = include_str!("../default_tile_sources.toml");
-
- lazy_static! {
- static ref PROJ_DIRS: ProjectDirs = ProjectDirs::from("", "", "DeltaMap");
- }
-
-
- #[derive(Debug)]
- pub struct Config {
- tile_cache_dir: PathBuf,
- sources: Vec<(String, TileSource)>,
- pbf_path: Option<PathBuf>,
- search_pattern: Option<String>,
- fps: f64,
- use_network: bool,
- async: bool,
- }
-
- impl Config {
- //TODO use builder pattern to create config
-
- pub fn from_arg_matches<'a>(matches: &clap::ArgMatches<'a>) -> Result<Config, String> {
- let mut config = if let Some(config_path) = matches.value_of_os("config") {
- Config::from_toml_file(config_path)?
- } else {
- Config::find_or_create()?
- };
-
- if let Some(tile_sources_path) = matches.value_of_os("tile-sources") {
- config.add_tile_sources_from_file(tile_sources_path)?;
- } else {
- config.add_tile_sources_from_default_or_create()?;
- };
-
- if let Some(os_path) = matches.value_of_os("pbf") {
- let path = PathBuf::from(os_path);
- if path.is_file() {
- config.pbf_path = Some(path);
- } else {
- return Err(format!("PBF file does not exist: {:?}", os_path));
- }
- }
-
- config.merge_arg_matches(matches);
-
- Ok(config)
- }
-
- fn merge_arg_matches<'a>(&mut self, matches: &clap::ArgMatches<'a>) {
- self.search_pattern = matches.value_of("search").map(|s| s.to_string());
-
- if let Some(Ok(fps)) = matches.value_of("fps").map(|s| s.parse()) {
- self.fps = fps;
- }
-
- if matches.is_present("offline") {
- self.use_network = false;
- }
-
- if matches.is_present("sync") {
- self.async = false;
- }
- }
-
- fn create_config_file<P: AsRef<Path> + Debug>(dir_path: P, file_path: P, contents: &[u8]) -> Result<(), String> {
- if !dir_path.as_ref().is_dir() {
- if let Err(err) = ::std::fs::create_dir_all(&dir_path) {
- return Err(format!("failed to create config directory ({:?}): {}",
- dir_path,
- err
- ));
- }
- }
-
- let mut file = File::create(&file_path)
- .map_err(|err| format!("failed to create config file {:?}: {}", &file_path, err))?;
-
- file.write_all(contents)
- .map_err(|err| format!(
- "failed to write contents to config file {:?}: {}",
- &file_path,
- err
- ))
- }
-
- fn find_or_create() -> Result<Config, String> {
- let config_dir = PROJ_DIRS.config_dir();
- let config_file = {
- let mut path = PathBuf::from(config_dir);
- path.push("config.toml");
- path
- };
-
- if config_file.is_file() {
- info!("load config from path {:?}", config_file);
-
- Config::from_toml_file(config_file)
- } else {
- // try to write a default config file
-
- match Config::create_config_file(
- config_dir,
- &config_file,
- DEFAULT_CONFIG.as_bytes()
- ) {
- Err(err) => warn!("{}", err),
- Ok(()) => info!("create default config file {:?}", config_file),
- }
-
- Config::from_toml_str(DEFAULT_CONFIG)
- }
- }
-
- fn add_tile_sources_from_default_or_create(&mut self) -> Result<(), String> {
- let config_dir = PROJ_DIRS.config_dir();
- let sources_file = {
- let mut path = PathBuf::from(config_dir);
- path.push("tile_sources.toml");
- path
- };
-
- if sources_file.is_file() {
- info!("load tile sources from path {:?}", sources_file);
-
- self.add_tile_sources_from_file(sources_file)
- } else {
- // try to write a default config file
-
- match Config::create_config_file(
- config_dir,
- &sources_file,
- DEFAULT_TILE_SOURCES.as_bytes()
- ) {
- Err(err) => warn!("{}", err),
- Ok(()) => info!("create default tile sources file {:?}", sources_file),
- }
-
- self.add_tile_sources_from_str(DEFAULT_TILE_SOURCES)
- }
- }
-
- /// Returns a tile cache directory path at a standard location. The returned path may not
- /// exist.
- fn default_tile_cache_dir() -> PathBuf {
- let mut path = PathBuf::from(PROJ_DIRS.cache_dir());
- path.push("tiles");
- path
- }
-
- 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 use_network = {
- match table.get("use_network") {
- Some(&Value::Boolean(x)) => x,
- Some(_) => return Err("use_network has to be a boolean.".to_string()),
- None => true,
- }
- };
-
- let async = {
- match table.get("async") {
- Some(&Value::Boolean(x)) => x,
- Some(_) => return Err("async has to be a boolean.".to_string()),
- None => true,
- }
- };
-
- Ok(
- Config {
- tile_cache_dir,
- sources: vec![],
- pbf_path: None,
- search_pattern: None,
- fps,
- use_network,
- async,
- }
- )
- },
- Ok(_) => Err("TOML file has invalid structure. Expected a Table as the top-level element.".to_string()),
- Err(e) => Err(format!("{}", e)),
- }
- }
-
- fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Config, String> {
- let mut file = File::open(path).map_err(|e| format!("{}", e))?;
-
- let mut content = String::new();
- file.read_to_string(&mut content).map_err(|e| format!("{}", e))?;
-
- Config::from_toml_str(&content)
- }
-
- fn add_tile_sources_from_str(&mut self, toml_str: &str) -> Result<(), String> {
- match toml_str.parse::<Value>() {
- Ok(Value::Table(ref table)) => {
- let sources_array = table.get("tile_sources")
- .ok_or_else(|| "missing \"tile_sources\" table".to_string())?
- .as_array()
- .ok_or_else(|| "\"tile_sources\" has to be an array.".to_string())?;
-
- for (id, source) in sources_array.iter().enumerate() {
- let name = source.get("name")
- .ok_or_else(|| "tile_source is missing \"name\" entry.".to_string())?
- .as_str()
- .ok_or_else(|| "\"name\" has to be a string".to_string())?;
-
- 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())?;
-
- //TODO reduce allowed strings to a reasonable subset of valid UTF-8 strings
- // that can also be used as a directory name or introduce a dir_name key with
- // more restrictions.
- if name.contains('/') || name.contains('\\') {
- return Err(format!("source name ({:?}) must not contain slashes (\"/\" or \"\\\")", name));
- }
-
- let mut path = PathBuf::from(&self.tile_cache_dir);
- path.push(name);
-
- self.sources.push((
- name.to_string(),
- TileSource::new(
- id as u32,
- url_template.to_string(),
- path,
- extension.to_string(),
- min_zoom as u32,
- max_zoom as u32,
- )?,
- ));
- }
- Ok(())
- },
- Ok(_) => Err("TOML file has invalid structure. Expected a Table as the top-level element.".to_string()),
- Err(e) => Err(format!("{}", e)),
- }
- }
-
- fn add_tile_sources_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<(), String> {
- let mut file = File::open(path).map_err(|e| format!("{}", e))?;
-
- let mut content = String::new();
- file.read_to_string(&mut content).map_err(|e| format!("{}", e))?;
-
- self.add_tile_sources_from_str(&content)
- }
-
- pub fn tile_sources(&self) -> &[(String, TileSource)] {
- &self.sources
- }
-
- pub fn pbf_path(&self) -> Option<&Path> {
- self.pbf_path.as_ref().map(|p| p.as_path())
- }
-
- pub fn search_pattern(&self) -> Option<&str> {
- self.search_pattern.as_ref().map(|s| s.as_str())
- }
-
- pub fn fps(&self) -> f64 {
- self.fps
- }
-
- pub fn use_network(&self) -> bool {
- self.use_network
- }
-
- pub fn async(&self) -> bool {
- self.async
- }
- }
-
- #[cfg(test)]
- mod tests {
- use config::*;
-
- #[test]
- fn default_config() {
- let mut config = Config::from_toml_str(DEFAULT_CONFIG).unwrap();
- config.add_tile_sources_from_str(DEFAULT_TILE_SOURCES).unwrap();
- }
- }
|