Change byte sequences to unicode sequences in ConfigManager
[wifi-radar.git] / wifiradar / config.py
blob25f8ed064238acb5a949f5a7a369d06ea5b15546
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 = unicode(section), unicode(option)
209 try:
210 self.set(section, option, value)
211 except configparser.NoSectionError:
212 self.add_section(section)
213 self.set_opt(section, option, value)
215 def set_bool_opt(self, section, option, value):
216 """ Set 'option' to boolean 'value' in 'section'.
218 'set_bool_opt' calls 'set_opt' after converting 'value' to
219 a string. Raises ValueError if 'value' is not a boolean,
220 where a boolean may be True, 'True', or a number greater
221 than 0; or False, 'False', or 0.
223 if isinstance(value, BooleanType):
224 # use False = 0 and True = 1 to return index into tuple
225 value = ('False', 'True')[value]
226 elif isinstance(value, IntType):
227 if value < 0:
228 raise ValueError(_('boolean value must be >= 0'))
229 # use False = 0 and True = 1 to return index into tuple
230 value = ('False', 'True')[value > 0]
231 elif isinstance(value, StringTypes):
232 # convert to title case (i.e. capital first letter, only)
233 value = value.title()
234 if value not in ('False', 'True'):
235 raise ValueError(_("value must be 'True' or 'False'"))
236 else:
237 raise ValueError(_('value cannot be converted to string'))
238 self.set_opt(section, option, value)
240 def set_int_opt(self, section, option, value):
241 """ Set 'option' to integer 'value' in 'section'.
243 'set_int_opt' calls 'set_opt' after converting 'value' to
244 a string. Raises TypeError if 'value' is not an integer.
246 if not isinstance(value, IntType):
247 raise TypeError(_('value is not an integer'))
248 self.set_opt(section, option, unicode(value))
250 def set_float_opt(self, section, option, value):
251 """ Set 'option' to float 'value' in 'section'.
253 'set_float_opt' calls 'set_opt' after converting 'value' to
254 a string. Raises TypeError if 'value' is not a float.
256 if not isinstance(value, (FloatType, IntType)):
257 raise TypeError(_('value is not a float or integer'))
258 self.set_opt(section, option, unicode(float(value)))
260 def profiles(self):
261 """ Return a list of the section names which denote AP profiles.
263 'profiles' does not return non-AP sections.
265 profile_list = []
266 for section in self.sections():
267 if ':' in section:
268 profile_list.append(section)
269 return profile_list
271 def update(self, config_manager):
272 """ Update internal configuration information using
273 :data:`config_manager`. This works by replacing the DEFAULT
274 and some non-profile sections in the configuration. All profiles,
275 and any non-profile sections not in :data:`config_manager`, are
276 left untouched during the update.
278 self._defaults = config_manager._defaults
279 for section in (set(config_manager.sections()) -
280 set(config_manager.profiles())):
281 self.set_section(section, dict(config_manager.items(section)))
284 class ConfigFileManager(ConfigManager):
285 """ Manage the configuration for the application, including reading
286 from and writing to a file.
288 def __init__(self, filename, defaults):
289 """ Create a new configuration file at 'filename' with DEFAULT
290 options and values in the 'defaults' dictionary.
292 ConfigManager.__init__(self, defaults)
293 self.filename = filename
295 def read(self):
296 """ Read configuration file from disk into instance variables.
298 fp = open(self.filename, "r")
299 self.readfp(fp)
300 # convert the auto_profile_order to a list for ordering
301 self.auto_profile_order = eval(self.get_opt('DEFAULT', 'auto_profile_order'))
302 for ap in self.profiles():
303 self.set_bool_opt(ap, 'known', True)
304 if ap in self.auto_profile_order: continue
305 self.auto_profile_order.append(ap)
306 fp.close()
307 # Remove any auto_profile_order AP without a matching section.
308 auto_profile_order_copy = self.auto_profile_order[:]
309 for ap in auto_profile_order_copy:
310 if ap not in self.profiles():
311 self.auto_profile_order.remove(ap)
313 def write(self):
314 """ Write configuration file to disk from instance variables.
316 Copied from configparser and modified to write options in
317 specific order.
319 self.set_opt('DEFAULT', 'auto_profile_order', unicode(self.auto_profile_order))
320 self.set_opt('DEFAULT', 'version', WIFI_RADAR_VERSION)
321 (fd, tempfilename) = tempfile.mkstemp(prefix="wifi-radar.conf.")
322 fp = os.fdopen(fd, "w")
323 # write DEFAULT section first
324 if self._defaults:
325 fp.write("[DEFAULT]\n")
326 for key in sorted(self._defaults.keys()):
327 fp.write("%s = %s\n" % (key, unicode(self._defaults[key]).replace('\n','\n\t')))
328 fp.write("\n")
329 # write other non-profile sections next
330 for section in self._sections:
331 if section not in self.profiles():
332 fp.write("[%s]\n" % section)
333 for key in sorted(self._sections[section].keys()):
334 if key != "__name__":
335 fp.write("%s = %s\n" %
336 (key, unicode(self._sections[section][key]).replace('\n', '\n\t')))
337 fp.write("\n")
338 # write profile sections
339 for section in self._sections:
340 if section in self.profiles():
341 fp.write("[%s]\n" % section)
342 for key in sorted(self._sections[section].keys()):
343 if key != "__name__":
344 fp.write("%s = %s\n" %
345 (key, unicode(self._sections[section][key]).replace('\n', '\n\t')))
346 fp.write("\n")
347 fp.close()
348 move(tempfilename, self.filename)
351 # Make so we can be imported
352 if __name__ == "__main__":
353 pass