| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- use clap;
- use directories::ProjectDirs;
- use query::QueryArgs;
- use session::Session;
- 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: Option<ProjectDirs> = ProjectDirs::from("", "", "DeltaMap");
- }
-
- fn proj_dirs_result() -> Result<&'static ProjectDirs, String> {
- PROJ_DIRS.as_ref().ok_or_else(|| "could not retrieve project directories".to_string())
- }
-
-
- #[derive(Debug)]
- pub struct Config {
- config_file_path: Option<PathBuf>,
- tile_sources_file_path: Option<PathBuf>,
- tile_cache_dir: PathBuf,
- sources: Vec<(String, TileSource)>,
- pbf_path: Option<PathBuf>,
- search_patterns: Vec<String>,
- keyval: Vec<(String, String)>,
- keyvalregex: Vec<(String, String)>,
- fps: f64,
- use_network: bool,
- async: bool,
- open_last_session: 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_patterns = matches.values_of("search").map_or_else(
- || vec![],
- |p| {
- p.map(|s| s.to_string()).collect()
- },
- );
-
- self.keyval = matches.values_of("keyval").map_or_else(
- || vec![],
- |mut kv| {
- let mut vec = vec![];
- loop {
- if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
- vec.push((k.to_string(), v.to_string()));
- } else {
- break;
- }
- }
- vec
- },
- );
-
- self.keyvalregex = matches.values_of("keyvalregex").map_or_else(
- || vec![],
- |mut kv| {
- let mut vec = vec![];
- loop {
- if let (Some(k), Some(v)) = (kv.next(), kv.next()) {
- vec.push((k.to_string(), v.to_string()));
- } else {
- break;
- }
- }
- vec
- },
- );
-
- 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 find_or_create() -> Result<Config, String> {
- let config_dir = proj_dirs_result()?.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 create_config_file(
- config_dir,
- &config_file,
- DEFAULT_CONFIG.as_bytes()
- ) {
- Err(err) => {
- warn!("{}", err);
- Config::from_toml_str::<&str>(DEFAULT_CONFIG, None)
- },
- Ok(()) => {
- info!("create default config file {:?}", config_file);
- Config::from_toml_str(DEFAULT_CONFIG, Some(config_file))
- },
- }
-
- }
- }
-
- fn add_tile_sources_from_default_or_create(&mut self) -> Result<(), String> {
- let config_dir = proj_dirs_result()?.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 create_config_file(
- config_dir,
- &sources_file,
- DEFAULT_TILE_SOURCES.as_bytes()
- ) {
- Err(err) => {
- warn!("{}", err);
- self.add_tile_sources_from_str::<&str>(DEFAULT_TILE_SOURCES, None)
- },
- Ok(()) => {
- info!("create default tile sources file {:?}", sources_file);
- self.add_tile_sources_from_str(DEFAULT_TILE_SOURCES, Some(sources_file))
- },
- }
-
- }
- }
-
- fn from_toml_str<P: AsRef<Path>>(toml_str: &str, config_path: Option<P>) -> 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 => {
- let mut path = PathBuf::from(proj_dirs_result()?.cache_dir());
- path.push("tiles");
- path
- },
- }
- };
-
- let pbf_path = {
- match table.get("pbf_file") {
- Some(&Value::String(ref pbf_file)) => {
- match config_path.as_ref() {
- Some(config_path) => {
- let p = config_path.as_ref().parent()
- .ok_or_else(|| "root path is not a valid config file.")?;
- let mut p = PathBuf::from(p);
- p.push(pbf_file);
- p = p.canonicalize().
- map_err(|e| format!("pbf_file ({:?}): {}", p, e))?;
- Some(p)
- },
- None => Some(PathBuf::from(pbf_file)),
- }
- },
- Some(_) => {
- return Err("pbf_file has to be a string.".to_string());
- },
- None => None,
- }
- };
-
- 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,
- }
- };
-
- let open_last_session = {
- match table.get("open_last_session") {
- Some(&Value::Boolean(x)) => x,
- Some(_) => return Err("open_last_session has to be a boolean.".to_string()),
- None => false,
- }
- };
-
- Ok(
- Config {
- config_file_path: config_path.map(|p| PathBuf::from(p.as_ref())),
- tile_sources_file_path: None,
- tile_cache_dir,
- sources: vec![],
- pbf_path,
- search_patterns: vec![],
- keyval: vec![],
- keyvalregex: vec![],
- fps,
- use_network,
- async,
- open_last_session,
- }
- )
- },
- 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, Some(path))
- }
-
- fn add_tile_sources_from_str<P>(
- &mut self,
- toml_str: &str,
- file_path: Option<P>
- ) -> Result<(), String>
- where P: AsRef<Path>
- {
- 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,
- )?,
- ));
- }
-
- self.tile_sources_file_path = file_path.map(|p| PathBuf::from(p.as_ref()));
- 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, Some(path))
- }
-
- pub fn list_paths(&self) {
- let config = match self.config_file_path.as_ref() {
- Some(path) => format!("{:?}", path),
- None => "<None>".to_string(),
- };
-
- let sources = match self.tile_sources_file_path.as_ref() {
- Some(path) => format!("{:?}", path),
- None => "<None>".to_string(),
- };
-
- let pbf = match self.pbf_path.as_ref() {
- Some(path) => format!("{:?}", path),
- None => "<None>".to_string(),
- };
-
- println!("\
- main configuration file: {}\n\
- tile sources file: {}\n\
- tile cache directory: {:?}\n\
- OSM PBF file: {}",
- config,
- sources,
- self.tile_cache_dir,
- pbf,
- );
- }
-
- 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_patterns(&self) -> &[String] {
- self.search_patterns.as_slice()
- }
-
- pub fn keyval(&self) -> &[(String, String)] {
- self.keyval.as_slice()
- }
-
- pub fn keyvalregex(&self) -> &[(String, String)] {
- self.keyvalregex.as_slice()
- }
-
- pub fn query_args(&self) -> Option<QueryArgs> {
- match (&self.search_patterns.first(), self.keyval.first(), self.keyvalregex.first()) {
- (&Some(ref pattern), None, None) => Some(
- if self.search_patterns.len() == 1 {
- QueryArgs::ValuePattern(pattern.to_string())
- } else {
- QueryArgs::Intersection(
- self.search_patterns.iter()
- .map(|s| QueryArgs::ValuePattern(s.to_string()))
- .collect()
- )
- }
- ),
- (&None, Some(keyval), None) => Some(
- if self.keyval.len() == 1 {
- QueryArgs::KeyValue(keyval.0.to_string(), keyval.1.to_string())
- } else {
- QueryArgs::Intersection(
- self.keyval.iter()
- .map(|(k, v)| QueryArgs::KeyValue(k.to_string(), v.to_string()))
- .collect()
- )
- }
- ),
- (&None, None, Some(keyvalregex)) => Some(
- if self.keyvalregex.len() == 1 {
- QueryArgs::KeyValueRegex(keyvalregex.0.to_string(), keyvalregex.1.to_string())
- } else {
- QueryArgs::Intersection(
- self.keyvalregex.iter()
- .map(|(k, v)| QueryArgs::KeyValueRegex(k.to_string(), v.to_string()))
- .collect()
- )
- }
- ),
- (pattern_opt, _, _) => {
- let mut vec = vec![];
-
- if let Some(ref pattern) = pattern_opt {
- vec.push(QueryArgs::ValuePattern(pattern.to_string()));
- }
-
- for (k, v) in &self.keyval {
- vec.push(QueryArgs::KeyValue(k.to_string(), v.to_string()));
- }
-
- for (k, v) in &self.keyvalregex {
- vec.push(QueryArgs::KeyValueRegex(k.to_string(), v.to_string()));
- }
-
- if vec.is_empty() {
- None
- } else {
- Some(QueryArgs::Intersection(vec))
- }
- },
- }
- }
-
- pub fn fps(&self) -> f64 {
- self.fps
- }
-
- pub fn use_network(&self) -> bool {
- self.use_network
- }
-
- pub fn async(&self) -> bool {
- self.async
- }
-
- pub fn open_last_session(&self) -> bool {
- self.open_last_session
- }
- }
-
- 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
- ))
- }
-
- pub fn read_last_session() -> Result<Session, String> {
- let session_path = {
- let config_dir = proj_dirs_result()?.config_dir();
- let mut path = PathBuf::from(config_dir);
- path.push("last_session.toml");
- path
- };
- Session::from_toml_file(session_path)
- }
-
- pub fn save_session(session: &Session) -> Result<(), String>
- {
- let config_dir = proj_dirs_result()?.config_dir();
- let session_path = {
- let mut path = PathBuf::from(config_dir);
- path.push("last_session.toml");
- path
- };
- let contents = session.to_toml_string();
- create_config_file(config_dir, &session_path, contents.as_bytes())
- }
-
-
- #[cfg(test)]
- mod tests {
- use config::*;
-
- #[test]
- fn default_config() {
- let mut config = Config::from_toml_str::<&str>(DEFAULT_CONFIG, None).unwrap();
- config.add_tile_sources_from_str::<&str>(DEFAULT_TILE_SOURCES, None).unwrap();
- }
- }
|