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
34 from ConfigParser
import NoOptionError
, NoSectionError
37 from configparser
import NoOptionError
, NoSectionError
39 from itertools
import chain
41 from multiprocessing
import Pipe
, Process
42 from threading
import Thread
44 from wifiradar
.config
import (copy_configuration
, make_section_name
,
46 from wifiradar
.connections
import ConnectionManager
, scanner
47 from wifiradar
.pubsub
import Dispatcher
, Message
48 import wifiradar
.misc
as misc
49 import wifiradar
.gui
.g2
as ui
50 import wifiradar
.gui
.g2
.transients
as transients
52 # Set up a logging framework.
53 logger
= logging
.getLogger("wifiradar")
57 """ The primary component of WiFi Radar.
59 def __init__(self
, config
):
60 """ Create WiFi Radar app using :data:`config` for configuration.
65 logger
.setLevel(logging
.DEBUG
)
67 logger
.setLevel(self
.config
.get_opt_as_int('DEFAULT', 'loglevel'))
69 dispatcher
= Dispatcher()
70 scanner_pipe
= dispatcher
.subscribe(['ALL'])
71 ui_pipe
= dispatcher
.subscribe(['ALL'])
74 fileLogHandler
= logging
.handlers
.RotatingFileHandler(self
.config
.get_opt('DEFAULT', 'logfile'), maxBytes
=64*1024, backupCount
=5)
76 error_pipe
= dispatcher
.subscribe()
77 error_pipe
.send(Message('ERROR',
78 'Cannot open log file for writing: %s.\n\n'.format(e
.strerror
) +
79 'WiFi Radar will work, but a log file will not be recorded.'))
80 dispatcher
.unsubscribe(error_pipe
)
82 fileLogHandler
.setFormatter(generic_formatter
)
83 logger
.addHandler(fileLogHandler
)
85 scanner_thread
= Thread(name
='scanner', target
=scanner
, args
=(config
, scanner_pipe
))
86 scanner_thread
.start()
88 ui_proc
= Process(name
='iu', target
=ui
.RadarWindow
, args
=(ui_pipe
,))
91 self
.msg_pipe
= dispatcher
.subscribe(['ALL'])
93 # This is the first run (or, at least, no config file was present),
94 # so pop up the preferences window
96 if self
.config
.get_opt_as_bool('DEFAULT', 'new_file'):
97 self
.config
.remove_option('DEFAULT', 'new_file')
98 config_copy
= ConfigManager({})
99 self
.msg_pipe
.send(Message('PREFS-EDIT', config_copy
))
100 except NoOptionError
:
102 # Add our known profiles in order.
103 for profile_name
in self
.config
.auto_profile_order
:
104 profile
= self
.config
.get_profile(profile_name
)
105 self
.msg_pipe
.send(Message('PROFILE-UPDATE', profile
))
111 scanner_thread
.join()
115 """ Watch for incoming messages and dispatch to subscribers.
119 msg
= self
.msg_pipe
.recv()
120 except (EOFError, IOError) as e
:
121 # This is bad, really bad.
122 logger
.critical('read on closed ' +
123 'Pipe ({}), failing...'.format(self
.msg_pipe
))
124 raise misc
.PipeError(e
)
126 self
._check
_message
(msg
)
128 def _check_message(self
, msg
):
131 if msg
.topic
== 'EXIT':
132 self
.msg_pipe
.close()
134 elif msg
.topic
== 'PROFILE-ORDER-UPDATE':
135 self
._profile
_order
_update
(msg
.details
)
136 elif msg
.topic
== 'PROFILE-EDIT-REQUEST':
137 essid
, bssid
= msg
.details
138 self
._profile
_edit
_request
(essid
, bssid
)
139 elif msg
.topic
== 'PROFILE-UPDATE':
140 self
._profile
_update
(msg
.details
)
141 elif msg
.topic
== 'PROFILE-REMOVE':
142 self
._profile
_remove
(msg
.details
)
143 elif msg
.topic
== 'PROFILE-REPLACE':
144 new_profile
, old_profile
= msg
.details
145 self
._profile
_replace
(new_profile
, old_profile
)
146 elif msg
.topic
== 'PREFS-EDIT-REQUEST':
147 self
._preferences
_edit
_request
()
148 elif msg
.topic
== 'PREFS-UPDATE':
149 self
._preferences
_update
(msg
.details
)
151 logger
.warning('unrecognized Message: "{}"'.format(msg
))
153 def _profile_order_update(self
, profile_order
):
156 self
.config
.auto_profile_order
= profile_order
158 def _profile_edit_request(self
, essid
, bssid
):
159 """ Send a message with a profile to be edited. If a profile with
160 :data:`essid` and :data:`bssid` is found in the list of known
161 profiles, that profile is sent for editing. Otherwise, a new
164 apname
= make_section_name(essid
, bssid
)
166 profile
= self
.config
.get_profile(apname
)
167 except NoSectionError
:
168 logger
.info('The profile "{}" does '.format(apname
) +
169 'not exist, creating a new profile.')
170 profile
= misc
.get_new_profile()
171 profile
['essid'] = essid
172 profile
['bssid'] = bssid
173 self
.msg_pipe
.send(Message('PROFILE-EDIT', profile
))
175 def _profile_update(self
, profile
):
178 if profile
['roaming']:
179 apname
= make_section_name(profile
['essid'], '')
181 apname
= make_section_name(profile
['essid'], profile
['bssid'])
182 if apname
not in self
.config
.profiles():
183 self
.config
.set_section(apname
, profile
)
184 self
.config
.auto_profile_order
.insert(0, apname
)
185 self
.msg_pipe
.send(Message('PROFILE-UPDATE', profile
))
186 self
.msg_pipe
.send(Message('PROFILE-MOVE', (profile
, 0)))
188 def _profile_remove(self
, apname
):
191 if apname
in self
.config
.profiles():
192 profile
= self
.config
.get_profile(apname
)
193 self
.config
.remove_section(apname
)
194 self
.config
.auto_profile_order
.remove(apname
)
195 self
.msg_pipe
.send(Message('PROFILE-UNLIST', profile
))
197 def _profile_replace(self
, new_profile
, old_profile
):
200 if old_profile
['roaming']:
201 apname
= make_section_name(old_profile
['essid'], '')
203 apname
= make_section_name(old_profile
['essid'],
204 old_profile
['bssid'])
205 if apname
in self
.config
.profiles():
206 old_position
= self
.config
.auto_profile_order
.index(apname
)
207 self
.config
.remove_section(apname
)
208 self
.config
.auto_profile_order
.remove(apname
)
209 if old_profile
['roaming']:
210 apname
= make_section_name(old_profile
['essid'], '')
212 apname
= make_section_name(old_profile
['essid'],
213 old_profile
['bssid'])
214 self
.config
.set_section(apname
, new_profile
)
215 self
.config
.auto_profile_order
.insert(old_position
, apname
)
216 self
.msg_pipe
.send(Message('PROFILE-UNLIST', old_profile
))
217 self
.msg_pipe
.send(Message('PROFILE-UPDATE', new_profile
))
218 self
.msg_pipe
.send(Message('PROFILE-MOVE', (new_profile
, old_position
)))
220 def _preferences_edit_request(self
):
221 """ Pass a :class:`ConfigManager` to the UI for editing.
223 config_copy
= copy_configuration(self
.config
)
224 self
.msg_pipe
.send(Message('PREFS-EDIT', config_copy
))
226 def _preferences_update(self
, config
):
227 """ Update configuration with :data:`config`.
229 self
.config
.update(config
)
233 self
.msg_pipe
.send(Message('ERROR',
234 'Could not save configuration file:\n' +
235 '{}\n\n{}'.format(self
.config
.filename
, e
.strerror
)))