A simple map viewer

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. use std::error::Error;
  2. use std::fs::File;
  3. use std::io::{Read, Write};
  4. use std::path::{Path, PathBuf};
  5. use tile_source::TileSource;
  6. use toml::Value;
  7. use xdg;
  8. static DEFAULT_CONFIG: &'static str = include_str!("../default_config.toml");
  9. #[derive(Debug)]
  10. pub struct Config {
  11. tile_cache_dir: PathBuf,
  12. sources: Vec<(String, TileSource)>,
  13. fps: f64,
  14. }
  15. impl Config {
  16. pub fn load() -> Result<Config, String> {
  17. if let Ok(xdg_dirs) = xdg::BaseDirectories::with_prefix("deltamap") {
  18. if let Some(config_path) = xdg_dirs.find_config_file("config.toml") {
  19. info!("load config from path {:?}", config_path);
  20. Config::from_toml_file(config_path)
  21. } else {
  22. // try to write a default config file
  23. if let Ok(path) = xdg_dirs.place_config_file("config.toml") {
  24. if let Ok(mut file) = File::create(&path) {
  25. if file.write_all(DEFAULT_CONFIG.as_bytes()).is_ok() {
  26. info!("write default config to {:?}", &path);
  27. }
  28. }
  29. }
  30. Config::from_toml_str(DEFAULT_CONFIG)
  31. }
  32. } else {
  33. info!("load default config");
  34. Config::from_toml_str(DEFAULT_CONFIG)
  35. }
  36. }
  37. /// Returns a tile cache directory path at a standard XDG cache location. The returned path may
  38. /// not exist.
  39. fn default_tile_cache_dir() -> Result<PathBuf, String> {
  40. let xdg_dirs = xdg::BaseDirectories::with_prefix("deltamap")
  41. .map_err(|e| e.description().to_string())?;
  42. match xdg_dirs.find_cache_file("tiles") {
  43. Some(dir) => Ok(dir),
  44. None => Ok(xdg_dirs.get_cache_home().join("tiles")),
  45. }
  46. }
  47. pub fn from_toml_str(toml_str: &str) -> Result<Config, String> {
  48. match toml_str.parse::<Value>() {
  49. Ok(Value::Table(ref table)) => {
  50. let tile_cache_dir = {
  51. match table.get("tile_cache_dir") {
  52. Some(dir) => {
  53. PathBuf::from(
  54. dir.as_str()
  55. .ok_or_else(|| "tile_cache_dir has to be a string".to_string())?
  56. )
  57. },
  58. None => Config::default_tile_cache_dir()?,
  59. }
  60. };
  61. let fps = {
  62. match table.get("fps") {
  63. Some(&Value::Float(fps)) => fps,
  64. Some(&Value::Integer(fps)) => fps as f64,
  65. Some(_) => return Err("fps has to be an integer or a float.".to_string()),
  66. None => 60.0,
  67. }
  68. };
  69. let sources_table = table.get("tile_sources")
  70. .ok_or_else(|| "missing \"tile_sources\" table".to_string())?
  71. .as_table()
  72. .ok_or_else(|| "\"tile_sources\" has to be a table".to_string())?;
  73. let mut sources_vec: Vec<(String, TileSource)> = Vec::with_capacity(sources_table.len());
  74. for (id, (name, source)) in sources_table.iter().enumerate() {
  75. let min_zoom = source.get("min_zoom")
  76. .unwrap_or_else(|| &Value::Integer(0))
  77. .as_integer()
  78. .ok_or_else(|| "min_zoom has to be an integer".to_string())
  79. .and_then(|m| {
  80. if m < 0 || m > 30 {
  81. Err(format!("min_zoom = {} is out of bounds, has to be in interval [0, 30]", m))
  82. } else {
  83. Ok(m)
  84. }
  85. })?;
  86. let max_zoom = source.get("max_zoom")
  87. .ok_or_else(|| format!("source {:?} is missing \"max_zoom\" entry", name))?
  88. .as_integer()
  89. .ok_or_else(|| "max_zoom has to be an integer".to_string())
  90. .and_then(|m| {
  91. if m < 0 || m > 30 {
  92. Err(format!("max_zoom = {} is out of bounds, has to be in interval [0, 30]", m))
  93. } else {
  94. Ok(m)
  95. }
  96. })?;
  97. if min_zoom > max_zoom {
  98. warn!("min_zoom ({}) and max_zoom ({}) allow no valid tiles", min_zoom, max_zoom);
  99. } else if min_zoom == max_zoom {
  100. warn!("min_zoom ({}) and max_zoom ({}) allow only one zoom level", min_zoom, max_zoom);
  101. }
  102. let url_template = source.get("url_template")
  103. .ok_or_else(|| format!("source {:?} is missing \"url_template\" entry", name))?
  104. .as_str()
  105. .ok_or_else(|| "url_template has to be a string".to_string())?;
  106. let extension = source.get("extension")
  107. .ok_or_else(|| format!("source {:?} is missing \"extension\" entry", name))?
  108. .as_str()
  109. .ok_or_else(|| "extension has to be a string".to_string())?;
  110. if name.contains('/') || name.contains('\\') {
  111. return Err(format!("source name ({:?}) must not contain slashes (\"/\" or \"\\\")", name));
  112. }
  113. let mut path = PathBuf::from(&tile_cache_dir);
  114. path.push(name);
  115. sources_vec.push((
  116. name.clone(),
  117. TileSource::new(
  118. id as u32,
  119. url_template.to_string(),
  120. path,
  121. extension.to_string(),
  122. min_zoom as u32,
  123. max_zoom as u32,
  124. ),
  125. ));
  126. }
  127. Ok(
  128. Config {
  129. tile_cache_dir: tile_cache_dir,
  130. sources: sources_vec,
  131. fps: fps,
  132. }
  133. )
  134. },
  135. Ok(_) => Err("TOML file has invalid structure. Expected a Table as the top-level element.".to_string()),
  136. Err(e) => Err(e.description().to_string()),
  137. }
  138. }
  139. pub fn from_toml_file<P: AsRef<Path>>(path: P) -> Result<Config, String> {
  140. let mut file = File::open(path).map_err(|e| e.description().to_string())?;
  141. let mut content = String::new();
  142. file.read_to_string(&mut content).map_err(|e| e.description().to_string())?;
  143. Config::from_toml_str(&content)
  144. }
  145. pub fn tile_sources(&self) -> &[(String, TileSource)] {
  146. &self.sources
  147. }
  148. pub fn fps(&self) -> f64 {
  149. self.fps
  150. }
  151. }
  152. #[cfg(test)]
  153. mod tests {
  154. use config::*;
  155. #[test]
  156. fn default_config() {
  157. assert!(Config::from_toml_str(DEFAULT_CONFIG).is_ok())
  158. }
  159. }