bbox_map_server/
config.rs

1use crate::wms_fcgi_backend::{MockFcgiBackend, QgisFcgiBackend, UmnFcgiBackend};
2use bbox_core::cli::CommonCommands;
3use bbox_core::config::{from_config_opt_or_exit, ConfigError};
4use bbox_core::service::ServiceConfig;
5use clap::{ArgMatches, FromArgMatches};
6use log::warn;
7use serde::Deserialize;
8use std::env;
9use std::ffi::OsStr;
10use std::fs::File;
11use std::path::Path;
12
13#[derive(Deserialize, Debug)]
14#[serde(default, deny_unknown_fields)]
15pub struct MapServiceCfg {
16    num_fcgi_processes: Option<usize>,
17    pub fcgi_client_pool_size: usize,
18    pub wait_timeout: Option<u64>,
19    pub create_timeout: Option<u64>,
20    pub recycle_timeout: Option<u64>,
21    pub qgis_backend: Option<QgisBackendCfg>,
22    pub umn_backend: Option<UmnBackendCfg>,
23    pub mock_backend: Option<MockBackendCfg>,
24    pub search_projects: bool,
25    pub default_project: Option<String>,
26}
27
28#[derive(Deserialize, Clone, Debug)]
29#[serde(deny_unknown_fields)]
30pub struct QgisBackendCfg {
31    pub exe_location: Option<String>,
32    pub project_basedir: String,
33    pub qgs: Option<QgisBackendSuffixCfg>,
34    pub qgz: Option<QgisBackendSuffixCfg>,
35}
36
37#[derive(Deserialize, Clone, Debug)]
38#[serde(deny_unknown_fields)]
39pub struct QgisBackendSuffixCfg {
40    pub path: String,
41}
42
43impl QgisBackendCfg {
44    pub fn new(basedir: &str) -> Self {
45        QgisBackendCfg {
46            exe_location: None,
47            project_basedir: basedir.to_string(),
48            qgs: Some(QgisBackendSuffixCfg {
49                path: "/qgis".to_string(),
50            }),
51            qgz: Some(QgisBackendSuffixCfg {
52                path: "/qgz".to_string(),
53            }),
54        }
55    }
56}
57
58#[derive(Deserialize, Clone, Debug)]
59#[serde(deny_unknown_fields)]
60pub struct UmnBackendCfg {
61    pub exe_location: Option<String>,
62    pub project_basedir: String,
63    pub path: String,
64}
65
66impl UmnBackendCfg {
67    pub fn new(basedir: &str) -> Self {
68        UmnBackendCfg {
69            exe_location: None,
70            project_basedir: basedir.to_string(),
71            path: "/wms/map".to_string(),
72        }
73    }
74}
75
76#[derive(Deserialize, Clone, Debug)]
77#[serde(deny_unknown_fields)]
78#[allow(dead_code)] // `MockBackendCfg` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
79pub struct MockBackendCfg {
80    pub path: String,
81}
82
83impl Default for MapServiceCfg {
84    fn default() -> Self {
85        let mut cfg = MapServiceCfg {
86            num_fcgi_processes: None,
87            fcgi_client_pool_size: 1,
88            wait_timeout: Some(90000),
89            create_timeout: Some(500),
90            recycle_timeout: Some(500),
91            qgis_backend: None,
92            umn_backend: None,
93            mock_backend: None,
94            // we want an inventory for the map viewer
95            search_projects: cfg!(feature = "inventory"),
96            default_project: None,
97        };
98        if let Ok(cwd) = env::current_dir().map(|p| p.into_os_string()) {
99            cfg.qgis_backend = Some(QgisBackendCfg::new(&cwd.to_string_lossy()));
100            cfg.umn_backend = Some(UmnBackendCfg::new(&cwd.to_string_lossy()));
101        }
102        cfg
103    }
104}
105
106impl ServiceConfig for MapServiceCfg {
107    fn initialize(cli: &ArgMatches) -> Result<Self, ConfigError> {
108        // Check if there is a backend configuration
109        let has_qgis_config =
110            from_config_opt_or_exit::<QgisBackendCfg>("mapserver.qgis_backend").is_some();
111        let has_umn_config =
112            from_config_opt_or_exit::<UmnBackendCfg>("mapserver.umn_backend").is_some();
113        let mut cfg: MapServiceCfg = from_config_opt_or_exit("mapserver").unwrap_or_default();
114
115        // Get config from CLI
116        if let Ok(CommonCommands::Serve(args)) = CommonCommands::from_arg_matches(cli) {
117            if let Some(file_or_url) = args.file_or_url {
118                // Set project_basedir from file_or_url
119                match Path::new(&file_or_url).extension().and_then(OsStr::to_str) {
120                    Some("qgs") | Some("qgz") => {
121                        if let Some(backend) = cfg.qgis_backend.as_mut() {
122                            if !has_qgis_config
123                                && set_backend_basedir(&mut backend.project_basedir, &file_or_url)
124                            {
125                                cfg.default_project = Some(file_or_url);
126                            }
127                        }
128                    }
129                    Some("map") => {
130                        if let Some(backend) = cfg.umn_backend.as_mut() {
131                            if !has_umn_config
132                                && set_backend_basedir(&mut backend.project_basedir, &file_or_url)
133                            {
134                                cfg.default_project = Some(file_or_url);
135                            }
136                        }
137                    }
138                    _ => { /* ignore other suffixes */ }
139                }
140            }
141        }
142        Ok(cfg)
143    }
144}
145
146impl MapServiceCfg {
147    pub fn num_fcgi_processes(&self) -> usize {
148        self.num_fcgi_processes.unwrap_or(num_cpus::get())
149    }
150}
151
152fn set_backend_basedir(project_basedir: &mut String, file_or_url: &str) -> bool {
153    if File::open(file_or_url).is_ok() {
154        if let Some(dir) = Path::new(file_or_url).parent() {
155            *project_basedir = dir.to_string_lossy().to_string();
156        }
157        true
158    } else {
159        warn!("Can't read file `{file_or_url}` - ignoring");
160        false
161    }
162}
163
164impl QgisBackendCfg {
165    pub fn backend(&self) -> QgisFcgiBackend {
166        QgisFcgiBackend::new(self.clone())
167    }
168}
169
170impl UmnBackendCfg {
171    pub fn backend(&self) -> UmnFcgiBackend {
172        UmnFcgiBackend::new(self.clone())
173    }
174}
175
176impl MockBackendCfg {
177    pub fn backend(&self) -> MockFcgiBackend {
178        MockFcgiBackend::new(self.clone())
179    }
180}