Use the new Py2/Py3 configparser import pattern
[wifi-radar.git] / wifiradar / config.py
blobd87695e96719844316b5f498f682a534d3e7cb54
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # config.py - support for WiFi Radar configuration
6 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
8 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
9 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
10 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
11 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
12 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
13 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
14 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
15 # Copyright (C) 2009-2010,2014 Sean Robinson <robinson@tuxfamily.org>
16 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
18 # This program is free software; you can redistribute it and/or modify
19 # it under the terms of the GNU General Public License as published by
20 # the Free Software Foundation; version 2 of the License.
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License in LICENSE.GPL for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to:
29 # Free Software Foundation, Inc.
30 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
34 from __future__ import unicode_literals
36 import logging
37 import os
38 import tempfile
39 from shutil import move
40 from subprocess import Popen, PIPE, STDOUT
41 from types import *
43 from wifiradar.misc import *
45 if PYVERSION < 3:
46 import ConfigParser as configparser
47 else:
48 import configparser
50 # create a logger
51 logger = logging.getLogger(__name__)
54 def copy_configuration(original, profiles=False):
55 """ Return a :class:`ConfigManager` copy of :dat:`original`. If
56 :data:`profiles` is False (the default), the copy does not include
57 the known AP profiles.
58 """
59 config_copy = ConfigManager()
60 config_copy._defaults = original._defaults.copy()
61 config_copy._sections = original._sections.copy()
62 # If not needed, remove the profiles from the new copy.
63 if not profiles:
64 for section in config_copy.profiles():
65 config_copy.remove_section(section)
66 return config_copy
68 def make_section_name(essid, bssid):
69 """ Returns the combined 'essid' and 'bssid' to make a config file
70 section name. 'essid' and 'bssid' are strings.
71 """
72 return essid + ':' + bssid
75 class ConfigManager(object, configparser.SafeConfigParser):
76 """ Manage configuration options.
77 """
78 def __init__(self, defaults=None):
79 """ Create a new configuration manger with DEFAULT options and
80 values in the 'defaults' dictionary.
81 """
82 configparser.SafeConfigParser.__init__(self, defaults)
83 self.auto_profile_order = []
85 def get_network_device(self):
86 """ Return the network device name.
88 If a device is specified in the configuration file,
89 'get_network_device' returns that value. If the configuration
90 is set to "auto-detect", this method returns the first
91 WiFi device as returned by iwconfig. 'get_network_device'
92 raises 'wifiradar.misc.NoDeviceError' if no wireless device
93 can be found in auto-detect mode.
94 """
95 device = self.get_opt('DEFAULT', 'interface')
96 if device == "auto_detect":
97 # auto detect network device
98 iwconfig_command = [
99 self.get_opt('DEFAULT', 'iwconfig_command'),
100 device]
101 try:
102 iwconfig_info = Popen(iwconfig_command, stdout=PIPE,
103 stderr=STDOUT).stdout
104 wireless_devices = list()
105 for line in iwconfig_info:
106 if '802.11' in line:
107 name = line[0:line.find(' ')]
108 wireless_devices.append(name)
109 # return the first device in the list
110 return wireless_devices[0]
111 except OSError as e:
112 logger.critical(_('problem auto-detecting wireless '
113 'device using iwconfig: {EXC}').format(EXC=e))
114 except IndexError:
115 logger.critical(_('No WiFi device found, '
116 'please set this in the preferences.'))
117 raise NoDeviceError('No WiFi device found.')
118 else:
119 # interface has been manually specified in configuration
120 return device
122 def set_section(self, section, dictionary):
123 """ Set the options of 'section' to values from 'dictionary'.
125 'section' will be created if it does not exist. The keys of
126 'dictionary' are the options and its values are the option
127 values.
129 for option, value in dictionary.items():
130 if isinstance(value, BooleanType):
131 self.set_bool_opt(section, option, value)
132 elif isinstance(value, IntType):
133 self.set_int_opt(section, option, value)
134 elif isinstance(value, FloatType):
135 self.set_float_opt(section, option, value)
136 else:
137 self.set_opt(section, option, value)
139 def get_profile(self, section):
140 """ Return the profile values in 'section' as a dictionary.
142 'get_profile' raises NoSectionError if the profile does not
143 exist.
145 str_types = ['bssid', 'channel', 'essid', 'protocol',
146 'con_prescript', 'con_postscript', 'dis_prescript',
147 'dis_postscript', 'key', 'mode', 'security',
148 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain',
149 'dns1', 'dns2']
150 bool_types = ['known', 'available', 'roaming', 'encrypted',
151 'use_wpa', 'use_dhcp']
152 int_types = ['signal']
153 profile = get_new_profile()
154 for option in bool_types:
155 try:
156 profile[option] = self.get_opt_as_bool(section, option)
157 except configparser.NoOptionError:
158 # A missing option means the default will be used.
159 pass
160 for option in int_types:
161 try:
162 profile[option] = self.get_opt_as_int(section, option)
163 except configparser.NoOptionError:
164 # A missing option means the default will be used.
165 pass
166 for option in str_types:
167 try:
168 profile[option] = self.get_opt(section, option)
169 except configparser.NoOptionError:
170 # A missing option means the default will be used.
171 pass
172 return profile
174 def get_opt(self, section, option):
175 """ Return the value of 'option' in 'section', as a string.
177 'section' and 'option' must be strings.
179 'get_opt' raises NoSectionError when 'section' is unknown and
180 NoOptionError when 'option' in unknown.
182 # False means to use interpolation when retrieving the value.
183 return self.get(section, option, False)
185 def get_opt_as_bool(self, section, option):
186 """ Return the value of 'option' in 'section', as a boolean.
188 return self.getboolean(section, option)
190 def get_opt_as_int(self, section, option):
191 """ Return the value of 'option' in 'section', as an integer.
193 return self.getint(section, option)
195 def get_opt_as_float(self, section, option):
196 """ Return the value of 'option' in 'section', as a float.
198 return self.getfloat(section, option)
200 def set_opt(self, section, option, value):
201 """ Set 'option' to 'value' in 'section'.
203 If 'section', does not exist in the configuration, it is
204 created. If 'section' and 'option' are not strings, each
205 will be turned into one before being added. Raises
206 TypeError if 'value' is not a string.
208 section, option = str(section), str(option)
209 if not isinstance(value, StringType):
210 raise TypeError(_('value must be a string type'))
211 try:
212 self.set(section, option, value)
213 except configparser.NoSectionError:
214 self.add_section(section)
215 self.set_opt(section, option, value)
217 def set_bool_opt(self, section, option, value):
218 """ Set 'option' to boolean 'value' in 'section'.
220 'set_bool_opt' calls 'set_opt' after converting 'value' to
221 a string. Raises ValueError if 'value' is not a boolean,
222 where a boolean may be True, 'True', or a number greater
223 than 0; or False, 'False', or 0.
225 if isinstance(value, BooleanType):
226 # use False = 0 and True = 1 to return index into tuple
227 value = ('False', 'True')[value]
228 elif isinstance(value, IntType):
229 if value < 0:
230 raise ValueError(_('boolean value must be >= 0'))
231 # use False = 0 and True = 1 to return index into tuple
232 value = ('False', 'True')[value > 0]
233 elif isinstance(value, StringType):
234 # convert to title case (i.e. capital first letter, only)
235 value = value.title()
236 if value not in ('False', 'True'):
237 raise ValueError(_("value must be 'True' or 'False'"))
238 else:
239 raise ValueError(_('value cannot be converted to string'))
240 self.set_opt(section, option, value)
242 def set_int_opt(self, section, option, value):
243 """ Set 'option' to integer 'value' in 'section'.
245 'set_int_opt' calls 'set_opt' after converting 'value' to
246 a string. Raises TypeError if 'value' is not an integer.
248 if not isinstance(value, IntType):
249 raise TypeError(_('value is not an integer'))
250 self.set_opt(section, option, str(value))
252 def set_float_opt(self, section, option, value):
253 """ Set 'option' to float 'value' in 'section'.
255 'set_float_opt' calls 'set_opt' after converting 'value' to
256 a string. Raises TypeError if 'value' is not a float.
258 if not isinstance(value, (FloatType, IntType)):
259 raise TypeError(_('value is not a float or integer'))
260 self.set_opt(section, option, str(float(value)))
262 def profiles(self):
263 """ Return a list of the section names which denote AP profiles.
265 'profiles' does not return non-AP sections.
267 profile_list = []
268 for section in self.sections():
269 if ':' in section:
270 profile_list.append(section)
271 return profile_list
273 def update(self, config_manager):
274 """ Update internal configuration information using
275 :data:`config_manager`. This works by replacing the DEFAULT
276 and some non-profile sections in the configuration. All profiles,
277 and any non-profile sections not in :data:`config_manager`, are
278 left untouched during the update.
280 self._defaults = config_manager._defaults
281 for section in (set(config_manager.sections()) -
282 set(config_manager.profiles())):
283 self.set_section(section, dict(config_manager.items(section)))
286 class ConfigFileManager(ConfigManager):
287 """ Manage the configuration for the application, including reading
288 from and writing to a file.
290 def __init__(self, filename, defaults):
291 """ Create a new configuration file at 'filename' with DEFAULT
292 options and values in the 'defaults' dictionary.
294 ConfigManager.__init__(self, defaults)
295 self.filename = filename
297 def read(self):
298 """ Read configuration file from disk into instance variables.
300 fp = open(self.filename, "r")
301 self.readfp(fp)
302 # convert the auto_profile_order to a list for ordering
303 self.auto_profile_order = eval(self.get_opt('DEFAULT', 'auto_profile_order'))
304 for ap in self.profiles():
305 self.set_bool_opt(ap, 'known', True)
306 if ap in self.auto_profile_order: continue
307 self.auto_profile_order.append(ap)
308 fp.close()
309 # Remove any auto_profile_order AP without a matching section.
310 auto_profile_order_copy = self.auto_profile_order[:]
311 for ap in auto_profile_order_copy:
312 if ap not in self.profiles():
313 self.auto_profile_order.remove(ap)
315 def write(self):
316 """ Write configuration file to disk from instance variables.
318 Copied from configparser and modified to write options in
319 specific order.
321 self.set_opt('DEFAULT', 'auto_profile_order', str(self.auto_profile_order))
322 self.set_opt('DEFAULT', 'version', WIFI_RADAR_VERSION)
323 (fd, tempfilename) = tempfile.mkstemp(prefix="wifi-radar.conf.")
324 fp = os.fdopen(fd, "w")
325 # write DEFAULT section first
326 if self._defaults:
327 fp.write("[DEFAULT]\n")
328 for key in sorted(self._defaults.keys()):
329 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
330 fp.write("\n")
331 # write other non-profile sections next
332 for section in self._sections:
333 if section not in self.profiles():
334 fp.write("[%s]\n" % section)
335 for key in sorted(self._sections[section].keys()):
336 if key != "__name__":
337 fp.write("%s = %s\n" %
338 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
339 fp.write("\n")
340 # write profile sections
341 for section in self._sections:
342 if section in self.profiles():
343 fp.write("[%s]\n" % section)
344 for key in sorted(self._sections[section].keys()):
345 if key != "__name__":
346 fp.write("%s = %s\n" %
347 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
348 fp.write("\n")
349 fp.close()
350 move(tempfilename, self.filename)
353 # Make so we can be imported
354 if __name__ == "__main__":
355 pass