Update wifiradar.misc import wherever simple to do so
[wifi-radar.git] / wifiradar / __init__.py
blobecdcf9af041529b168e24f62b89462944b1eb1dd
1 # -*- coding: utf-8 -*-
3 # __init__.py - main logic for operating WiFi Radar
5 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
7 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
8 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
9 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
10 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
11 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
12 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
13 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
14 # Copyright (C) 2009-2010,2014 Sean Robinson <seankrobinson@gmail.com>
15 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
17 # This program is free software; you can redistribute it and/or modify
18 # it under the terms of the GNU General Public License as published by
19 # the Free Software Foundation; version 2 of the License.
21 # This program is distributed in the hope that it will be useful,
22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 # GNU General Public License in LICENSE.GPL for more details.
26 # You should have received a copy of the GNU General Public License
27 # along with this program; if not, write to the Free Software
28 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 from itertools import chain
33 import logging
34 import logging.handlers
35 from multiprocessing import Pipe, Process
36 from threading import Thread
38 from wifiradar.config import (copy_configuration, make_section_name,
39 ConfigManager)
40 from wifiradar.connections import ConnectionManager, scanner
41 from wifiradar.pubsub import Dispatcher, Message
42 from wifiradar.misc import _, PYVERSION, PipeError, get_new_profile
43 import wifiradar.gui.g2 as ui
44 import wifiradar.gui.g2.transients as transients
46 if PYVERSION < 3:
47 from ConfigParser import NoOptionError, NoSectionError
48 else:
49 from configparser import NoOptionError, NoSectionError
51 # Set up a logging framework.
52 logger = logging.getLogger("wifiradar")
55 class Main(object):
56 """ The primary component of WiFi Radar.
57 """
58 def __init__(self, config):
59 """ Create WiFi Radar app using :data:`config` for configuration.
60 """
61 self.config = config
63 dispatcher = Dispatcher()
64 scanner_pipe = dispatcher.subscribe(['ALL'])
65 ui_pipe = dispatcher.subscribe(['ALL'])
67 try:
68 fileLogHandler = logging.handlers.RotatingFileHandler(self.config.get_opt('DEFAULT', 'logfile'), maxBytes=64*1024, backupCount=5)
69 except IOError as e:
70 error_pipe = dispatcher.subscribe()
71 error_pipe.send(Message('ERROR',
72 'Cannot open log file for writing: {ERR}.\n\n'
73 'WiFi Radar will work, but a log file will not '
74 'be recorded.'.format(ERR=e.strerror)))
75 dispatcher.unsubscribe(error_pipe)
76 else:
77 fileLogHandler.setFormatter(generic_formatter)
78 logger.addHandler(fileLogHandler)
80 scanner_thread = Thread(name='scanner', target=scanner, args=(config, scanner_pipe))
81 scanner_thread.start()
83 ui_proc = Process(name='ui', target=ui.start, args=(ui_pipe,))
84 ui_proc.start()
86 self.msg_pipe = dispatcher.subscribe(['ALL'])
88 # This is the first run (or, at least, no config file was present),
89 # so pop up the preferences window
90 try:
91 if self.config.get_opt_as_bool('DEFAULT', 'new_file'):
92 self.config.remove_option('DEFAULT', 'new_file')
93 config_copy = ConfigManager({})
94 self.msg_pipe.send(Message('PREFS-EDIT', config_copy))
95 except NoOptionError:
96 pass
97 # Add our known profiles in order.
98 for profile_name in self.config.auto_profile_order:
99 profile = self.config.get_profile(profile_name)
100 self.msg_pipe.send(Message('PROFILE-UPDATE', profile))
101 self.running = True
102 try:
103 self.run()
104 finally:
105 ui_proc.join()
106 scanner_thread.join()
107 dispatcher.close()
109 def run(self):
110 """ Watch for incoming messages and dispatch to subscribers.
112 while self.running:
113 try:
114 msg = self.msg_pipe.recv()
115 except (EOFError, IOError) as e:
116 # This is bad, really bad.
117 logger.critical('read on closed Pipe '
118 '({PIPE}), failing...'.format(PIPE=self.msg_pipe))
119 raise PipeError(e)
120 else:
121 self._check_message(msg)
123 def _check_message(self, msg):
126 if msg.topic == 'EXIT':
127 self.msg_pipe.close()
128 self.running = False
129 elif msg.topic == 'PROFILE-ORDER-UPDATE':
130 self._profile_order_update(msg.details)
131 elif msg.topic == 'PROFILE-EDIT-REQUEST':
132 essid, bssid = msg.details
133 self._profile_edit_request(essid, bssid)
134 elif msg.topic == 'PROFILE-EDITED':
135 new_profile, old_profile = msg.details
136 self._profile_replace(new_profile, old_profile)
137 elif msg.topic == 'PROFILE-REMOVE':
138 self._profile_remove(msg.details)
139 elif msg.topic == 'PREFS-EDIT-REQUEST':
140 self._preferences_edit_request()
141 elif msg.topic == 'PREFS-UPDATE':
142 self._preferences_update(msg.details)
143 else:
144 logger.warning('unrecognized Message: "{MSG}"'.format(MSG=msg))
146 def _profile_order_update(self, profile_order):
147 """ Update the auto profile order in the configuration.
148 :data:`profile_order` is a list of profile names, in order.
150 self.config.auto_profile_order = profile_order
151 try:
152 self.config.write()
153 except IOError as e:
154 self.msg_pipe.send(Message('ERROR',
155 'Could not save configuration file:\n'
156 '{FILE}\n\n{ERR}'.format(FILE=self.config.filename,
157 ERR=e.strerror)))
159 def _profile_edit_request(self, essid, bssid):
160 """ Send a message with a profile to be edited. If a profile with
161 :data:`essid` and :data:`bssid` is found in the list of known
162 profiles, that profile is sent for editing. Otherwise, a new
163 profile is sent.
165 apname = make_section_name(essid, bssid)
166 try:
167 profile = self.config.get_profile(apname)
168 except NoSectionError:
169 logger.info('The profile "{NAME}" does not exist, '
170 'creating a new profile.'.format(NAME=apname))
171 profile = get_new_profile()
172 profile['essid'] = essid
173 profile['bssid'] = bssid
174 self.msg_pipe.send(Message('PROFILE-EDIT', profile))
176 def _profile_replace(self, new_profile, old_profile):
177 """ Update :data:`old_profile` with :data:`new_profile`.
179 new_apname = make_section_name(new_profile['essid'],
180 new_profile['bssid'])
181 old_apname = make_section_name(old_profile['essid'],
182 old_profile['bssid'])
183 if old_apname == new_apname:
184 # Simple update of old_profile with new_profile.
185 self.config.set_section(new_apname, new_profile)
186 self.msg_pipe.send(Message('PROFILE-UPDATE', new_profile))
187 else:
188 # Replace old_profile with new_profile.
189 old_position = self._profile_remove(old_apname)
190 # Add the updated profile like it's new...
191 self.config.set_section(new_apname, new_profile)
192 self.msg_pipe.send(Message('PROFILE-UPDATE', new_profile))
193 if old_position is not None:
194 # ..., but in the old position.
195 self.config.auto_profile_order.insert(old_position, new_apname)
196 self.msg_pipe.send(Message('PROFILE-MOVE', (old_position, new_profile)))
197 if old_profile['known'] is False and new_profile['known'] is True:
198 # The profile has been upgraded from scanned to configured.
199 self.config.auto_profile_order.insert(0, new_apname)
200 self.msg_pipe.send(Message('PROFILE-MOVE', (0, new_profile)))
201 try:
202 self.config.write()
203 except IOError as e:
204 self.msg_pipe.send(Message('ERROR',
205 'Could not save configuration file:\n'
206 '{FILE}\n\n{ERR}'.format(FILE=self.config.filename,
207 ERR=e.strerror)))
209 def _profile_remove(self, apname):
210 """ Remove the profile named in :data:`apname`. This method returns
211 the index in the auto-profile order at which the name was found,
212 or None if not matched.
214 try:
215 position = self.config.auto_profile_order.index(apname)
216 except ValueError:
217 return None
218 else:
219 profile = self.config.get_profile(apname)
220 self.config.remove_section(apname)
221 self.config.auto_profile_order.remove(apname)
222 self.msg_pipe.send(Message('PROFILE-UNLIST', profile))
223 try:
224 self.config.write()
225 except IOError as e:
226 self.msg_pipe.send(Message('ERROR',
227 'Could not save configuration file:\n'
228 '{FILE}\n\n{ERR}'.format(FILE=self.config.filename,
229 ERR=e.strerror)))
230 return position
232 def _preferences_edit_request(self):
233 """ Pass a :class:`ConfigManager` to the UI for editing.
235 config_copy = copy_configuration(self.config)
236 self.msg_pipe.send(Message('PREFS-EDIT', config_copy))
238 def _preferences_update(self, config):
239 """ Update configuration with :data:`config`.
241 self.config.update(config)
242 try:
243 self.config.write()
244 except IOError as e:
245 self.msg_pipe.send(Message('ERROR',
246 'Could not save configuration file:\n'
247 '{FILE}\n\n{ERR}'.format(FILE=self.config.filename,
248 ERR=e.strerror)))