Use Python's auto-concatenation of strings within parenthesis
[wifi-radar.git] / wifiradar / config.py
bloba6f637a4c635bcdc049ae402567d70fa5ece8d97
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 <seankrobinson@gmail.com>
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 the Free Software
29 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 try:
34 # Py2
35 import ConfigParser as configparser
36 except ImportError:
37 # Py3
38 import configparser
40 import logging
41 import os
42 import tempfile
43 from shutil import move
44 from subprocess import Popen, PIPE, STDOUT
45 from types import *
47 import wifiradar.misc as misc
49 # create a logger
50 logger = logging.getLogger(__name__)
53 def copy_configuration(original, profiles=False):
54 """ Return a :class:`ConfigManager` copy of :dat:`original`. If
55 :data:`profiles` is False (the default), the copy does not include
56 the known AP profiles.
57 """
58 config_copy = ConfigManager()
59 config_copy._defaults = original._defaults.copy()
60 config_copy._sections = original._sections.copy()
61 # If not needed, remove the profiles from the new copy.
62 if not profiles:
63 for section in config_copy.profiles():
64 config_copy.remove_section(section)
65 return config_copy
67 def make_section_name(essid, bssid):
68 """ Returns the combined 'essid' and 'bssid' to make a config file
69 section name. 'essid' and 'bssid' are strings.
70 """
71 return essid + ':' + bssid
74 class ConfigManager(object, configparser.SafeConfigParser):
75 """ Manage configuration options.
76 """
77 def __init__(self, defaults=None):
78 """ Create a new configuration manger with DEFAULT options and
79 values in the 'defaults' dictionary.
80 """
81 configparser.SafeConfigParser.__init__(self, defaults)
82 self.auto_profile_order = []
84 def get_network_device(self):
85 """ Return the network device name.
87 If a device is specified in the configuration file,
88 'get_network_device' returns that value. If the configuration
89 is set to "auto-detect", this method returns the first
90 WiFi device as returned by iwconfig. 'get_network_device'
91 raises 'wifiradar.misc.NoDeviceError' if no wireless device
92 can be found in auto-detect mode.
93 """
94 device = self.get_opt('DEFAULT', 'interface')
95 if device == "auto_detect":
96 # auto detect network device
97 iwconfig_command = [
98 self.get_opt('DEFAULT', 'iwconfig_command'),
99 device]
100 try:
101 iwconfig_info = Popen(iwconfig_command, stdout=PIPE,
102 stderr=STDOUT).stdout
103 wireless_devices = list()
104 for line in iwconfig_info:
105 if '802.11' in line:
106 name = line[0:line.find(' ')]
107 wireless_devices.append(name)
108 # return the first device in the list
109 return wireless_devices[0]
110 except OSError as e:
111 logger.critical('problem auto-detecting wireless '
112 'device using iwconfig: {EXC}'.format(EXC=e))
113 except IndexError:
114 logger.critical('No WiFi device found, '
115 'please set this in the preferences.')
116 raise misc.NoDeviceError('No WiFi device found.')
117 else:
118 # interface has been manually specified in configuration
119 return device
121 def set_section(self, section, dictionary):
122 """ Set the options of 'section' to values from 'dictionary'.
124 'section' will be created if it does not exist. The keys of
125 'dictionary' are the options and its values are the option
126 values.
128 for option, value in dictionary.items():
129 if isinstance(value, BooleanType):
130 self.set_bool_opt(section, option, value)
131 elif isinstance(value, IntType):
132 self.set_int_opt(section, option, value)
133 elif isinstance(value, FloatType):
134 self.set_float_opt(section, option, value)
135 else:
136 self.set_opt(section, option, value)
138 def get_profile(self, section):
139 """ Return the profile values in 'section' as a dictionary.
141 'get_profile' raises NoSectionError if the profile does not
142 exist.
144 str_types = ['bssid', 'channel', 'essid', 'protocol',
145 'con_prescript', 'con_postscript', 'dis_prescript',
146 'dis_postscript', 'key', 'mode', 'security',
147 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain',
148 'dns1', 'dns2']
149 bool_types = ['known', 'available', 'roaming', 'encrypted',
150 'use_wpa', 'use_dhcp']
151 int_types = ['signal']
152 profile = misc.get_new_profile()
153 for option in bool_types:
154 try:
155 profile[option] = self.get_opt_as_bool(section, option)
156 except configparser.NoOptionError:
157 # A missing option means the default will be used.
158 pass
159 for option in int_types:
160 try:
161 profile[option] = self.get_opt_as_int(section, option)
162 except configparser.NoOptionError:
163 # A missing option means the default will be used.
164 pass
165 for option in str_types:
166 try:
167 profile[option] = self.get_opt(section, option)
168 except configparser.NoOptionError:
169 # A missing option means the default will be used.
170 pass
171 return profile
173 def get_opt(self, section, option):
174 """ Return the value of 'option' in 'section', as a string.
176 'section' and 'option' must be strings.
178 'get_opt' raises NoSectionError when 'section' is unknown and
179 NoOptionError when 'option' in unknown.
181 # False means to use interpolation when retrieving the value.
182 return self.get(section, option, False)
184 def get_opt_as_bool(self, section, option):
185 """ Return the value of 'option' in 'section', as a boolean.
187 return self.getboolean(section, option)
189 def get_opt_as_int(self, section, option):
190 """ Return the value of 'option' in 'section', as an integer.
192 return self.getint(section, option)
194 def get_opt_as_float(self, section, option):
195 """ Return the value of 'option' in 'section', as a float.
197 return self.getfloat(section, option)
199 def set_opt(self, section, option, value):
200 """ Set 'option' to 'value' in 'section'.
202 If 'section', does not exist in the configuration, it is
203 created. If 'section' and 'option' are not strings, each
204 will be turned into one before being added. Raises
205 TypeError if 'value' is not a string.
207 section, option = str(section), str(option)
208 if not isinstance(value, StringType):
209 raise TypeError('value must be a string type')
210 try:
211 self.set(section, option, value)
212 except configparser.NoSectionError:
213 self.add_section(section)
214 self.set_opt(section, option, value)
216 def set_bool_opt(self, section, option, value):
217 """ Set 'option' to boolean 'value' in 'section'.
219 'set_bool_opt' calls 'set_opt' after converting 'value' to
220 a string. Raises ValueError if 'value' is not a boolean,
221 where a boolean may be True, 'True', or a number greater
222 than 0; or False, 'False', or 0.
224 if isinstance(value, BooleanType):
225 # use False = 0 and True = 1 to return index into tuple
226 value = ('False', 'True')[value]
227 elif isinstance(value, IntType):
228 if value < 0:
229 raise ValueError('boolean value must be >= 0')
230 # use False = 0 and True = 1 to return index into tuple
231 value = ('False', 'True')[value > 0]
232 elif isinstance(value, StringType):
233 # convert to title case (i.e. capital first letter, only)
234 value = value.title()
235 if value not in ('False', 'True'):
236 raise ValueError("value must be 'True' or 'False'")
237 else:
238 raise ValueError('value cannot be converted to string')
239 self.set_opt(section, option, value)
241 def set_int_opt(self, section, option, value):
242 """ Set 'option' to integer 'value' in 'section'.
244 'set_int_opt' calls 'set_opt' after converting 'value' to
245 a string. Raises TypeError if 'value' is not an integer.
247 if not isinstance(value, IntType):
248 raise TypeError('value is not an integer')
249 self.set_opt(section, option, str(value))
251 def set_float_opt(self, section, option, value):
252 """ Set 'option' to float 'value' in 'section'.
254 'set_float_opt' calls 'set_opt' after converting 'value' to
255 a string. Raises TypeError if 'value' is not a float.
257 if not isinstance(value, (FloatType, IntType)):
258 raise TypeError('value is not a float or integer')
259 self.set_opt(section, option, str(float(value)))
261 def profiles(self):
262 """ Return a list of the section names which denote AP profiles.
264 'profiles' does not return non-AP sections.
266 profile_list = []
267 for section in self.sections():
268 if ':' in section:
269 profile_list.append(section)
270 return profile_list
272 def update(self, config_manager):
273 """ Update internal configuration information using
274 :data:`config_manager`. This works by replacing the DEFAULT
275 and some non-profile sections in the configuration. All profiles,
276 and any non-profile sections not in :data:`config_manager`, are
277 left untouched during the update.
279 self._defaults = config_manager._defaults
280 for section in (set(config_manager.sections()) -
281 set(config_manager.profiles())):
282 self.set_section(section, dict(config_manager.items(section)))
285 class ConfigFileManager(ConfigManager):
286 """ Manage the configuration for the application, including reading
287 from and writing to a file.
289 def __init__(self, filename, defaults):
290 """ Create a new configuration file at 'filename' with DEFAULT
291 options and values in the 'defaults' dictionary.
293 ConfigManager.__init__(self, defaults)
294 self.filename = filename
296 def read(self):
297 """ Read configuration file from disk into instance variables.
299 fp = open(self.filename, "r")
300 self.readfp(fp)
301 # convert the auto_profile_order to a list for ordering
302 self.auto_profile_order = eval(self.get_opt('DEFAULT', 'auto_profile_order'))
303 for ap in self.profiles():
304 self.set_bool_opt(ap, 'known', True)
305 if ap in self.auto_profile_order: continue
306 self.auto_profile_order.append(ap)
307 fp.close()
308 # Remove any auto_profile_order AP without a matching section.
309 auto_profile_order_copy = self.auto_profile_order[:]
310 for ap in auto_profile_order_copy:
311 if ap not in self.profiles():
312 self.auto_profile_order.remove(ap)
314 def write(self):
315 """ Write configuration file to disk from instance variables.
317 Copied from configparser and modified to write options in
318 specific order.
320 self.set_opt('DEFAULT', 'auto_profile_order', str(self.auto_profile_order))
321 self.set_opt('DEFAULT', 'version', misc.WIFI_RADAR_VERSION)
322 (fd, tempfilename) = tempfile.mkstemp(prefix="wifi-radar.conf.")
323 fp = os.fdopen(fd, "w")
324 # write DEFAULT section first
325 if self._defaults:
326 fp.write("[DEFAULT]\n")
327 for key in sorted(self._defaults.keys()):
328 fp.write("%s = %s\n" % (key, str(self._defaults[key]).replace('\n','\n\t')))
329 fp.write("\n")
330 # write other non-profile sections next
331 for section in self._sections:
332 if section not in self.profiles():
333 fp.write("[%s]\n" % section)
334 for key in sorted(self._sections[section].keys()):
335 if key != "__name__":
336 fp.write("%s = %s\n" %
337 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
338 fp.write("\n")
339 # write profile sections
340 for section in self._sections:
341 if section in self.profiles():
342 fp.write("[%s]\n" % section)
343 for key in sorted(self._sections[section].keys()):
344 if key != "__name__":
345 fp.write("%s = %s\n" %
346 (key, str(self._sections[section][key]).replace('\n', '\n\t')))
347 fp.write("\n")
348 fp.close()
349 move(tempfilename, self.filename)
352 # Make so we can be imported
353 if __name__ == "__main__":
354 pass