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 <robinson@tuxfamily.org>
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:
28 # Free Software Foundation, Inc.
29 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
33 from __future__
import unicode_literals
36 import logging
.handlers
37 from multiprocessing
import Pipe
, Process
38 from threading
import Thread
40 from wifiradar
.config
import (copy_configuration
, make_section_name
,
42 from wifiradar
.connections
import ConnectionManager
, scanner
43 from wifiradar
.pubsub
import Dispatcher
, Message
44 from wifiradar
.misc
import _
, PYVERSION
, PipeError
, get_new_profile
45 import wifiradar
.gui
.g2
as ui
46 import wifiradar
.gui
.g2
.transients
as transients
49 from ConfigParser
import NoOptionError
, NoSectionError
51 from configparser
import NoOptionError
, NoSectionError
53 # Set up a logging framework.
54 logger
= logging
.getLogger(__name__
)
58 """ The primary component of WiFi Radar.
60 def __init__(self
, config
):
61 """ Create WiFi Radar app using :data:`config` for configuration.
65 dispatcher
= Dispatcher()
66 scanner_pipe
= dispatcher
.subscribe(['ALL'])
67 ui_pipe
= dispatcher
.subscribe(['ALL'])
70 fileLogHandler
= logging
.handlers
.RotatingFileHandler(self
.config
.get_opt('DEFAULT', 'logfile'), maxBytes
=64*1024, backupCount
=5)
72 error_pipe
= dispatcher
.subscribe()
73 error_pipe
.send(Message('ERROR',
74 _('Cannot open log file for writing: {ERR}.\n\n'
75 'WiFi Radar will work, but a log file will not '
76 'be recorded.').format(ERR
=e
.strerror
)))
77 dispatcher
.unsubscribe(error_pipe
)
79 fileLogHandler
.setFormatter(generic_formatter
)
80 logger
.addHandler(fileLogHandler
)
82 scanner_thread
= Thread(name
='scanner', target
=scanner
, args
=(config
, scanner_pipe
))
83 scanner_thread
.start()
85 ui_proc
= Process(name
='ui', target
=ui
.start
, args
=(ui_pipe
,))
88 self
.msg_pipe
= dispatcher
.subscribe(['ALL'])
90 # This is the first run (or, at least, no config file was present),
91 # so pop up the preferences window
93 if self
.config
.get_opt_as_bool('DEFAULT', 'new_file'):
94 self
.config
.remove_option('DEFAULT', 'new_file')
95 config_copy
= copy_configuration(self
.config
)
96 self
.msg_pipe
.send(Message('PREFS-EDIT', config_copy
))
99 # Add our known profiles in order.
100 for profile_name
in self
.config
.auto_profile_order
:
101 profile
= self
.config
.get_profile(profile_name
)
102 self
.msg_pipe
.send(Message('PROFILE-UPDATE', profile
))
108 scanner_thread
.join()
112 """ Watch for incoming messages and dispatch to subscribers.
116 msg
= self
.msg_pipe
.recv()
117 except (EOFError, IOError) as e
:
118 # This is bad, really bad.
119 logger
.critical(_('read on closed Pipe ({PIPE}), '
120 'failing...').format(PIPE
=self
.msg_pipe
))
123 self
._check
_message
(msg
)
125 def _check_message(self
, msg
):
128 if msg
.topic
== 'EXIT':
129 self
.msg_pipe
.close()
131 elif msg
.topic
== 'PROFILE-ORDER-UPDATE':
132 self
._profile
_order
_update
(msg
.details
)
133 elif msg
.topic
== 'PROFILE-EDIT-REQUEST':
134 essid
, bssid
= msg
.details
135 self
._profile
_edit
_request
(essid
, bssid
)
136 elif msg
.topic
== 'PROFILE-EDITED':
137 new_profile
, old_profile
= msg
.details
138 self
._profile
_replace
(new_profile
, old_profile
)
139 elif msg
.topic
== 'PROFILE-REMOVE':
140 self
._profile
_remove
(msg
.details
)
141 elif msg
.topic
== 'PREFS-EDIT-REQUEST':
142 self
._preferences
_edit
_request
()
143 elif msg
.topic
== 'PREFS-UPDATE':
144 self
._preferences
_update
(msg
.details
)
146 logger
.warning(_('unrecognized Message: "{MSG}"').format(MSG
=msg
))
148 def _profile_order_update(self
, profile_order
):
149 """ Update the auto profile order in the configuration.
150 :data:`profile_order` is a list of profile names, in order.
152 self
.config
.auto_profile_order
= profile_order
156 self
.msg_pipe
.send(Message('ERROR',
157 _('Could not save configuration file:\n'
158 '{FILE}\n\n{ERR}').format(FILE
=self
.config
.filename
,
161 def _profile_edit_request(self
, essid
, bssid
):
162 """ Send a message with a profile to be edited. If a profile with
163 :data:`essid` and :data:`bssid` is found in the list of known
164 profiles, that profile is sent for editing. Otherwise, a new
167 apname
= make_section_name(essid
, bssid
)
169 profile
= self
.config
.get_profile(apname
)
170 except NoSectionError
:
171 logger
.info(_('The profile "{NAME}" does not exist, '
172 'creating a new profile.').format(NAME
=apname
))
173 profile
= get_new_profile()
174 profile
['essid'] = essid
175 profile
['bssid'] = bssid
176 self
.msg_pipe
.send(Message('PROFILE-EDIT', profile
))
178 def _profile_replace(self
, new_profile
, old_profile
):
179 """ Update :data:`old_profile` with :data:`new_profile`.
181 new_apname
= make_section_name(new_profile
['essid'],
182 new_profile
['bssid'])
183 old_apname
= make_section_name(old_profile
['essid'],
184 old_profile
['bssid'])
185 if old_apname
== new_apname
:
186 # Simple update of old_profile with new_profile.
187 self
.config
.set_section(new_apname
, new_profile
)
188 self
.msg_pipe
.send(Message('PROFILE-UPDATE', new_profile
))
190 # Replace old_profile with new_profile.
191 old_position
= self
._profile
_remove
(old_apname
)
192 # Add the updated profile like it's new...
193 self
.config
.set_section(new_apname
, new_profile
)
194 self
.msg_pipe
.send(Message('PROFILE-UPDATE', new_profile
))
195 if old_position
is not None:
196 # ..., but in the old position.
197 self
.config
.auto_profile_order
.insert(old_position
, new_apname
)
198 self
.msg_pipe
.send(Message('PROFILE-MOVE', (old_position
, new_profile
)))
199 if old_profile
['known'] is False and new_profile
['known'] is True:
200 # The profile has been upgraded from scanned to configured.
201 self
.config
.auto_profile_order
.insert(0, new_apname
)
202 self
.msg_pipe
.send(Message('PROFILE-MOVE', (0, new_profile
)))
206 self
.msg_pipe
.send(Message('ERROR',
207 _('Could not save configuration file:\n'
208 '{FILE}\n\n{ERR}').format(FILE
=self
.config
.filename
,
211 def _profile_remove(self
, apname
):
212 """ Remove the profile named in :data:`apname`. This method returns
213 the index in the auto-profile order at which the name was found,
214 or None if not matched.
217 position
= self
.config
.auto_profile_order
.index(apname
)
221 profile
= self
.config
.get_profile(apname
)
222 self
.config
.remove_section(apname
)
223 self
.config
.auto_profile_order
.remove(apname
)
224 self
.msg_pipe
.send(Message('PROFILE-UNLIST', profile
))
228 self
.msg_pipe
.send(Message('ERROR',
229 _('Could not save configuration file:\n'
230 '{FILE}\n\n{ERR}').format(FILE
=self
.config
.filename
,
234 def _preferences_edit_request(self
):
235 """ Pass a :class:`ConfigManager` to the UI for editing.
237 config_copy
= copy_configuration(self
.config
)
238 self
.msg_pipe
.send(Message('PREFS-EDIT', config_copy
))
240 def _preferences_update(self
, config
):
241 """ Update configuration with :data:`config`.
243 self
.config
.update(config
)
247 self
.msg_pipe
.send(Message('ERROR',
248 _('Could not save configuration file:\n'
249 '{FILE}\n\n{ERR}').format(FILE
=self
.config
.filename
,