1# http://pyrocko.org - GPLv3
2#
3# The Pyrocko Developers, 21st Century
4# ---|P------/S----------~Lg----------
6import os
7import os.path as op
8from copy import deepcopy
9import logging
11from . import util
12from .guts import Object, Float, String, load, dump, List, Dict, TBase, \
13 Tuple, StringChoice, Bool
16logger = logging.getLogger('pyrocko.config')
18guts_prefix = 'pf'
20pyrocko_dir_tmpl = os.environ.get(
21 'PYROCKO_DIR',
22 os.path.join('~', '.pyrocko'))
25def make_conf_path_tmpl(name='config'):
26 return op.join(pyrocko_dir_tmpl, '%s.pf' % name)
29default_phase_key_mapping = {
30 'F1': 'P', 'F2': 'S', 'F3': 'R', 'F4': 'Q', 'F5': '?'}
33class BadConfig(Exception):
34 pass
37class PathWithPlaceholders(String):
38 '''
39 Path, possibly containing placeholders.
40 '''
41 pass
44class VisibleLengthSetting(Object):
45 class __T(TBase):
46 def regularize_extra(self, val):
47 if isinstance(val, list):
48 return self.cls(key=val[0], value=val[1])
50 return val
52 def to_save(self, val):
53 return (val.key, val.value)
55 def to_save_xml(self, val):
56 raise NotImplementedError()
58 key = String.T()
59 value = Float.T()
62class ConfigBase(Object):
63 @classmethod
64 def default(cls):
65 return cls()
68class SnufflerConfig(ConfigBase):
69 visible_length_setting = List.T(
70 VisibleLengthSetting.T(),
71 default=[VisibleLengthSetting(key='Short', value=20000.),
72 VisibleLengthSetting(key='Medium', value=60000.),
73 VisibleLengthSetting(key='Long', value=120000.),
74 VisibleLengthSetting(key='Extra Long', value=600000.)])
75 phase_key_mapping = Dict.T(
76 String.T(), String.T(), default=default_phase_key_mapping)
77 demean = Bool.T(default=True)
78 show_scale_ranges = Bool.T(default=False)
79 show_scale_axes = Bool.T(default=False)
80 trace_scale = String.T(default='individual_scale')
81 show_boxes = Bool.T(default=True)
82 clip_traces = Bool.T(default=True)
83 first_start = Bool.T(default=True)
85 def get_phase_name(self, key):
86 return self.phase_key_mapping.get('F%s' % key, 'Undefined')
89class PyrockoConfig(ConfigBase):
90 cache_dir = PathWithPlaceholders.T(
91 default=os.path.join(pyrocko_dir_tmpl, 'cache'))
92 earthradius = Float.T(default=6371.*1000.)
93 fdsn_timeout = Float.T(default=None, optional=True)
94 gf_store_dirs = List.T(PathWithPlaceholders.T())
95 gf_store_superdirs = List.T(PathWithPlaceholders.T())
96 topo_dir = PathWithPlaceholders.T(
97 default=os.path.join(pyrocko_dir_tmpl, 'topo'))
98 tectonics_dir = PathWithPlaceholders.T(
99 default=os.path.join(pyrocko_dir_tmpl, 'tectonics'))
100 geonames_dir = PathWithPlaceholders.T(
101 default=os.path.join(pyrocko_dir_tmpl, 'geonames'))
102 crustdb_dir = PathWithPlaceholders.T(
103 default=os.path.join(pyrocko_dir_tmpl, 'crustdb'))
104 gshhg_dir = PathWithPlaceholders.T(
105 default=os.path.join(pyrocko_dir_tmpl, 'gshhg'))
106 leapseconds_path = PathWithPlaceholders.T(
107 default=os.path.join(pyrocko_dir_tmpl, 'leap-seconds.list'))
108 leapseconds_url = String.T(
109 default='https://www.ietf.org/timezones/data/leap-seconds.list')
110 earthdata_credentials = Tuple.T(
111 2, String.T(),
112 optional=True)
113 gui_toolkit = StringChoice.T(
114 choices=['auto', 'qt4', 'qt5'],
115 default='auto')
116 use_high_precision_time = Bool.T(default=False)
119config_cls = {
120 'config': PyrockoConfig,
121 'snuffler': SnufflerConfig
122}
125def fill_template(tmpl, config_type):
126 tmpl = tmpl .format(
127 module=('.' + config_type) if config_type != 'pyrocko' else '')
128 return tmpl
131def expand(x):
132 x = op.expanduser(op.expandvars(x))
133 return x
136def rec_expand(x):
137 for prop, val in x.T.ipropvals(x):
138 if prop.multivalued:
139 if val is not None:
140 for i, ele in enumerate(val):
141 if isinstance(prop.content_t, PathWithPlaceholders.T):
142 newele = expand(ele)
143 if newele != ele:
144 val[i] = newele
146 elif isinstance(ele, Object):
147 rec_expand(ele)
148 else:
149 if isinstance(prop, PathWithPlaceholders.T):
150 newval = expand(val)
151 if newval != val:
152 setattr(x, prop.name, newval)
154 elif isinstance(val, Object):
155 rec_expand(val)
158def processed(config):
159 config = deepcopy(config)
160 rec_expand(config)
161 return config
164def mtime(p):
165 return os.stat(p).st_mtime
168g_conf_mtime = {}
169g_conf = {}
172def raw_config(config_name='config'):
174 conf_path = expand(make_conf_path_tmpl(config_name))
176 if not op.exists(conf_path):
177 g_conf[config_name] = config_cls[config_name].default()
178 write_config(g_conf[config_name], config_name)
180 conf_mtime_now = mtime(conf_path)
181 if conf_mtime_now != g_conf_mtime.get(config_name, None):
182 g_conf[config_name] = load(filename=conf_path)
183 if not isinstance(g_conf[config_name], config_cls[config_name]):
184 with open(conf_path, 'r') as fconf:
185 logger.warning('Config file content:')
186 for line in fconf:
187 logger.warning(' ' + line)
189 raise BadConfig('config file does not contain a '
190 'valid "%s" section. Found: %s' % (
191 config_cls[config_name].__name__,
192 type(g_conf[config_name])))
194 g_conf_mtime[config_name] = conf_mtime_now
196 return g_conf[config_name]
199def config(config_name='config'):
200 return processed(raw_config(config_name))
203def write_config(conf, config_name='config'):
204 conf_path = expand(make_conf_path_tmpl(config_name))
205 util.ensuredirs(conf_path)
206 dump(conf, filename=conf_path)
209override_gui_toolkit = None
212def effective_gui_toolkit():
213 return override_gui_toolkit or config().gui_toolkit