Clean up beginning of requesting a profile edit
[wifi-radar.git] / wifiradar / __init__.py
blobcda3cc1f6339608030a68c2f56125813bca4bc9a
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 try:
33 # Py2
34 from ConfigParser import NoOptionError, NoSectionError
35 except ImportError:
36 # Py3
37 from configparser import NoOptionError, NoSectionError
39 from itertools import chain
40 import logging
41 from multiprocessing import Pipe, Process
42 from threading import Thread
44 from wifiradar.config import (copy_configuration, make_section_name,
45 ConfigManager)
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")
56 class Main(object):
57 """ The primary component of WiFi Radar.
58 """
59 def __init__(self, config):
60 """ Create WiFi Radar app using :data:`config` for configuration.
61 """
62 self.config = config
64 if __debug__:
65 logger.setLevel(logging.DEBUG)
66 else:
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'])
73 try:
74 fileLogHandler = logging.handlers.RotatingFileHandler(self.config.get_opt('DEFAULT', 'logfile'), maxBytes=64*1024, backupCount=5)
75 except IOError as e:
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)
81 else:
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,))
89 ui_proc.start()
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
95 try:
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:
101 pass
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))
106 self.running = True
107 try:
108 self.run()
109 finally:
110 ui_proc.join()
111 scanner_thread.join()
112 dispatcher.close()
114 def run(self):
115 """ Watch for incoming messages and dispatch to subscribers.
117 while self.running:
118 try:
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)
125 else:
126 self._check_message(msg)
128 def _check_message(self, msg):
131 if msg.topic == 'EXIT':
132 self.msg_pipe.close()
133 self.running = False
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)
150 else:
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
162 profile is sent.
164 apname = make_section_name(essid, bssid)
165 try:
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'], '')
180 else:
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'], '')
202 else:
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'], '')
211 else:
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)
230 try:
231 self.config.write()
232 except IOError as e:
233 self.msg_pipe.send(Message('ERROR',
234 'Could not save configuration file:\n' +
235 '{}\n\n{}'.format(self.config.filename, e.strerror)))