fix proxy support
[straw.git] / src / lib / Config.py
blobd30d4a9467cf9050c568cfb77436b05b287086df
1 """ Config.py
3 Module for Straw's configuration settings.
4 """
5 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
6 __license__ = """ GNU General Public License
8 This program is free software; you can redistribute it and/or modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation; either version 2 of the License, or (at your option) any later
11 version.
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along with
18 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 Place - Suite 330, Boston, MA 02111-1307, USA. """
21 import cPickle, os, traceback
22 import urllib
23 from error import log, logtb, logparam
24 import pygtk
25 pygtk.require('2.0')
26 import gconf
27 import Event
29 GCONF_STRAW_ROOT = "/apps/straw"
31 OPTION_LAST_POLL = "/general/last_poll"
32 OPTION_ITEMS_STORED = "/general/number_of_items_stored"
33 OPTION_ITEM_ORDER = "/general/item_order_newest"
34 OPTION_BROWSER_CMD = "/general/browser_cmd"
35 OPTION_WINDOW_SIZE_W = "/ui/window_width"
36 OPTION_WINDOW_SIZE_H = "/ui/window_height"
37 OPTION_MAIN_PANE_POS = "/ui/main_pane_position"
38 OPTION_SUB_PANE_POS = "/ui/sub_pane_position"
39 OPTION_WINDOW_MAX = "/ui/window_maximized"
40 OPTION_MAGNIFICATION = "/ui/text_magnification"
41 OPTION_OFFLINE = "/general/offline"
42 OPTION_POLL_FREQUENCY = "/general/poll_frequency"
43 OPTION_FEED_ID_SEQ = "feed_id_seq"
44 OPTION_FEEDS = "feeds"
45 OPTION_CATEGORIES = "categories"
47 def ensure_directory(strawdir):
48 if os.path.exists(strawdir):
49 if not os.path.isdir(strawdir):
50 raise Exception
51 return 0
52 os.mkdir(strawdir)
53 return 1
55 #############
56 # Config persistence classes
57 class ConfigPicklePersistence:
58 def __init__(self, filename):
59 self._config_file = filename
60 self._dict = None
62 def save_option(self, option, value):
63 if self._dict is None:
64 self._initialize_dict()
65 temp_config_file = self._config_file + ".working"
66 pickle_file = open(temp_config_file, "w")
67 self._dict[option] = value
68 cPickle.dump(self._dict, pickle_file, True)
69 pickle_file.close()
70 os.rename(temp_config_file, self._config_file)
71 return
73 def load_option(self, option):
74 if self._dict is None:
75 self._initialize_dict()
76 return self._dict.get(option, None)
78 def _initialize_dict(self):
79 if os.path.exists(self._config_file):
80 pickle_file = open(self._config_file, "r")
81 self._dict = cPickle.load(pickle_file)
82 pickle_file.close()
83 else:
84 self._dict = {}
86 class ConfigGConfPersistence:
87 SAVERS = {OPTION_LAST_POLL: 'int',
88 OPTION_ITEMS_STORED: 'int',
89 OPTION_ITEM_ORDER: 'bool',
90 OPTION_BROWSER_CMD: 'string',
91 OPTION_WINDOW_SIZE_W: 'int',
92 OPTION_WINDOW_SIZE_H: 'int',
93 OPTION_MAIN_PANE_POS: 'int',
94 OPTION_SUB_PANE_POS: 'int',
95 OPTION_OFFLINE: 'bool',
96 OPTION_WINDOW_MAX: 'bool',
97 OPTION_MAGNIFICATION: 'float',
98 OPTION_POLL_FREQUENCY: 'int'}
100 def __init__(self, client):
101 self.client = client
103 def save_option(self, option, value):
104 getattr(self.client, 'set_' + self.SAVERS[option])(
105 GCONF_STRAW_ROOT + option, value)
107 def load_option(self, option):
108 return getattr(self.client, 'get_' + self.SAVERS[option])(
109 GCONF_STRAW_ROOT + option)
111 class ConfigPersistence:
112 def __init__(self, *backends):
113 self.backends = backends
115 def save_option(self, option, value):
116 for b in self.backends:
117 if option in b[1]:
118 b[0].save_option(option, value)
120 def load_option(self, option):
121 for b in self.backends:
122 if option in b[1]:
123 return b[0].load_option(option)
125 class ProxyConfig(object):
126 def __init__(self):
127 self._active = False
128 self._host = None
129 self._port = None
130 self._auth = None
131 self._username = None
132 self._password = None
134 @apply
135 def active():
136 doc = "this will be true when one of the subclasses is used"
137 def fget(self):
138 return self._active
139 def fset(self, active):
140 self._active = active
141 return property(**locals())
143 @apply
144 def host():
145 doc = "the host name where the proxy server lives"
146 def fget(self):
147 return self._host
148 def fset(self, host):
149 self._host = host
150 return property(**locals())
152 @apply
153 def port():
154 doc = "the port of the proxy server to connect to"
155 def fget(self):
156 return self._port
157 def fset(self, port):
158 self._port = port
159 return property(**locals())
161 @apply
162 def auth():
163 doc = "true if we need to authenticate to the proxy server"
164 def fget(self):
165 return self._auth
166 def fset(self, isauth):
167 self._auth = isauth
168 return property(**locals())
170 @apply
171 def username():
172 doc = "the username to use when authenticating to the proxy"
173 def fget(self):
174 return self._username
175 def fset(self, username):
176 self._username = username
177 return property(**locals())
179 @apply
180 def password():
181 doc = "the password to use when authenticating to the proxy"
182 def fget(self):
183 return self._password
184 def fset(self, password):
185 self._password = password
186 return property(**locals())
189 class GconfProxyConfig(ProxyConfig):
190 """Encapsulate proxy use and location information (host, port, ip),
191 gconf reading and name lookup logic"""
193 GCONF_HTTP_PROXY = "/system/http_proxy"
194 GCONF_HTTP_PROXY_USE = GCONF_HTTP_PROXY + "/use_http_proxy"
195 GCONF_HTTP_PROXY_HOST = GCONF_HTTP_PROXY + "/host"
196 GCONF_HTTP_PROXY_PORT = GCONF_HTTP_PROXY + "/port"
197 GCONF_HTTP_PROXY_AUTHENTICATION = GCONF_HTTP_PROXY + "/use_authentication"
198 GCONF_HTTP_PROXY_USER = GCONF_HTTP_PROXY + "/authentication_user"
199 GCONF_HTTP_PROXY_PASSWORD = GCONF_HTTP_PROXY + "/authentication_password"
201 def __init__(self):
202 ProxyConfig.__init__(self)
203 client = gconf.client_get_default()
204 self.active = client.dir_exists(self.GCONF_HTTP_PROXY)
205 if not self.active:
206 return
208 client.add_dir(self.GCONF_HTTP_PROXY, gconf.CLIENT_PRELOAD_RECURSIVE)
209 client.notify_add(self.GCONF_HTTP_PROXY_USE, self.proxy_mode_changed)
210 client.notify_add(self.GCONF_HTTP_PROXY_HOST, self.proxy_host_changed)
211 client.notify_add(self.GCONF_HTTP_PROXY_PORT, self.proxy_port_changed)
212 client.notify_add(self.GCONF_HTTP_PROXY_AUTHENTICATION, self.proxy_auth_changed)
213 client.notify_add(self.GCONF_HTTP_PROXY_USER, self.proxy_auth_username_changed)
214 client.notify_add(self.GCONF_HTTP_PROXY_PASSWORD, self.proxy_auth_password_changed)
216 self.use = client.get_bool(self.GCONF_HTTP_PROXY_USE)
217 self.host = client.get_string(self.GCONF_HTTP_PROXY_HOST)
218 self.port = client.get_int(self.GCONF_HTTP_PROXY_PORT)
219 self.auth = client.get_bool(self.GCONF_HTTP_PROXY_AUTHENTICATION)
220 if self.auth:
221 self.username = client.get_string(self.GCONF_HTTP_PROXY_USER)
222 self.password = client.get_string(self.GCONF_HTTP_PROXY_PASSWORD)
224 # here be gconf logic
225 def proxy_mode_changed(self, client, notify_id, entry, *args):
226 self.use = entry.value.get_bool()
228 def proxy_host_changed(self, client, notify_id, entry, *args):
229 value = entry.value.get_string()
230 if value:
231 self.host = value
232 else:
233 self.use = 0
235 def proxy_port_changed(self, client, notify_id, entry, *args):
236 value = entry.value.get_int()
237 if value:
238 self.port = value
239 else:
240 self.use = 0
242 def proxy_auth_changed(self, client, notify_id, entry, *args):
243 value = entry.value.get_bool()
244 if value:
245 self.auth = value
247 def proxy_auth_username_changed(self, client, notify_id, entry, *args):
248 value = entry.value.get_string()
249 self.username = value
251 def proxy_auth_password_changed(self, client, notify_id, entry, *args):
252 value = entry.value.get_string()
253 self.password = value
255 class EnvironmentProxyConfig(ProxyConfig):
256 """Encapsulate proxy use and location information, environment reading"""
258 def __init__(self):
259 ProxyConfig.__init__(self)
260 _read_env()
262 def _read_env(self):
263 proxy = None
264 proxies = urllib.getproxies()
265 for key, value in proxies.iteritems():
266 proxy = proxies.get(key)
267 user, authority = urllib.splituser(proxy)
268 if authority:
269 self.host, self.port = urllib.splitport(authority)
270 self.active = True
271 if user:
272 self.username, self.password = urllib.splitpasswd(user)
273 self.auth = True
274 return
276 class Config(object, Event.SignalEmitter):
277 _straw_dir = os.path.join(os.getenv("HOME"), ".straw")
278 _straw_config_file = os.path.join(_straw_dir, "config")
280 def __init__(self, persistence):
281 Event.SignalEmitter.__init__(self)
282 self.persistence = persistence
283 self.initialize_slots(Event.ItemOrderChangedSignal,
284 Event.OfflineModeChangedSignal,
285 Event.PollFrequencyChangedSignal,
286 Event.NumberOfItemsStoredChangedSignal)
287 self._feed_id_seq = 0
288 self._poll_freq = 1800
289 self._last_poll = 0
290 self._browser_cmd = ''
291 self._items_stored = 30
292 self._item_order = False
293 self._main_window_size = (640,480)
294 self._main_pane_position = 100
295 self._sub_pane_position = 100
296 self.first_time = None
297 self._offline = True
298 self._window_maximized = False
299 self._reload_css = False
300 self._proxy = None
302 def initialize_proxy(self):
303 # GConfProxyConfig has precedence over EnvironmentProxyConfig
304 pcchoices = (GconfProxyConfig, EnvironmentProxyConfig)
305 for p in pcchoices:
306 self._proxy = p()
307 if self._proxy.active:
308 break
310 def initialize_options(self):
311 # Call this after calling Config's constructor
312 self.first_time = ensure_directory(self._straw_dir)
314 if os.path.exists(self.straw_config_file):
315 self._feed_id_seq = self.persistence.load_option(OPTION_FEED_ID_SEQ)
317 self._poll_freq = self.persistence.load_option(OPTION_POLL_FREQUENCY)
318 self._last_poll = self.persistence.load_option(OPTION_LAST_POLL)
319 self._items_stored = self.persistence.load_option(OPTION_ITEMS_STORED)
320 self._browser_cmd = self.persistence.load_option(OPTION_BROWSER_CMD)
321 self._item_order = self.persistence.load_option(OPTION_ITEM_ORDER)
323 width = self.persistence.load_option(OPTION_WINDOW_SIZE_W)
324 height = self.persistence.load_option(OPTION_WINDOW_SIZE_H)
325 if width <= 0:
326 width = 640
327 if height <= 0:
328 height = 480
329 self._main_window_size = (width, height)
331 self._main_pane_position = self.persistence.load_option(
332 OPTION_MAIN_PANE_POS)
333 self._sub_pane_position = self.persistence.load_option(
334 OPTION_SUB_PANE_POS)
335 self._window_maximized = self.persistence.load_option(OPTION_WINDOW_MAX)
336 self._text_magnification = self.persistence.load_option(OPTION_MAGNIFICATION)
337 self._offline = self.persistence.load_option(OPTION_OFFLINE)
339 @property
340 def straw_dir(self):
341 return self._straw_dir
343 @property
344 def straw_config_file(self):
345 return self._straw_config_file
347 @property
348 def proxy(self):
349 return self._proxy
351 @apply
352 def feeds():
353 doc = "Marshalled feed data"
354 def fget(self):
355 return self.persistence.load_option(OPTION_FEEDS)
356 def fset(self, feeddata):
357 self.persistence.save_option(
358 OPTION_FEEDS, feeddata)
359 return property(**locals())
361 @apply
362 def categories():
363 doc = ""
364 def fget(self):
365 return self.persistence.load_option(OPTION_CATEGORIES)
366 def fset(self, categorydata):
367 self.persistence.save_option(
368 OPTION_CATEGORIES, categorydata)
369 return property(**locals())
371 @apply
372 def poll_frequency():
373 doc = "Polling frequency"
374 def fget(self):
375 return self._poll_freq
376 def fset(self, poll_frequency):
377 if self._poll_freq != poll_frequency:
378 self._poll_freq = poll_frequency
379 self.persistence.save_option(OPTION_POLL_FREQUENCY, poll_frequency)
380 self.emit_signal(Event.PollFrequencyChangedSignal(self, poll_frequency))
381 return property(**locals())
383 @apply
384 def last_poll():
385 doc = "last poll"
386 def fget(self):
387 return self._last_poll
388 def fset(self, last_poll):
389 if self._last_poll != last_poll:
390 self._last_poll = last_poll
391 self.persistence.save_option(OPTION_LAST_POLL, last_poll)
392 return property(**locals())
394 @apply
395 def browser_cmd():
396 doc = "The browser to use"
397 def fget(self):
398 return self._browser_cmd
399 def fset(self, browser_cmd):
400 if self._browser_cmd != browser_cmd:
401 self._browser_cmd = browser_cmd
402 self.persistence.save_option(OPTION_BROWSER_CMD, browser_cmd)
403 return property(**locals())
405 @apply
406 def number_of_items_stored():
407 doc = "Number of items to store per feed"
408 def fget(self):
409 return self._items_stored
410 def fset(self, num=30):
411 if self._items_stored != num:
412 self._items_stored = num
413 self.persistence.save_option(OPTION_ITEMS_STORED, num)
414 self.emit_signal(Event.NumberOfItemsStoredChangedSignal(self))
415 return property(**locals())
417 @apply
418 def item_order():
419 doc = "Ordering of Items"
420 def fget(self):
421 return self._item_order
422 def fset(self, order):
423 if self._item_order != order:
424 self._item_order = order
425 self.persistence.save_option(OPTION_ITEM_ORDER, order)
426 self.emit_signal(Event.ItemOrderChangedSignal(self))
427 return property(**locals())
429 @apply
430 def feed_id_seq():
431 doc = ""
432 def fget(self):
433 return self._feed_id_seq
434 def fset(self, id):
435 self._feed_id_seq = id
436 self.persistence.save_option(OPTION_FEED_ID_SEQ, id)
437 return property(**locals())
439 def next_feed_id_seq(self):
440 self.feed_id_seq += 1
441 return self._feed_id_seq
443 @apply
444 def main_window_size():
445 doc = ""
446 def fget(self):
447 return self._main_window_size
448 def fset(self, size):
449 if self._main_window_size != size:
450 self._main_window_size = size
451 self.persistence.save_option(OPTION_WINDOW_SIZE_W, size[0])
452 self.persistence.save_option(OPTION_WINDOW_SIZE_H, size[1])
453 return property(**locals())
455 @apply
456 def offline():
457 doc = ""
458 def fget(self):
459 return self._offline
460 def fset(self, mode):
461 if self._offline != mode:
462 self._offline = mode
463 self.persistence.save_option(OPTION_OFFLINE, mode)
464 self.emit_signal(Event.OfflineModeChangedSignal(self))
465 return property(**locals())
467 @apply
468 def window_maximized():
469 doc = ""
470 def fget(self):
471 return self._window_maximized
472 def fset(self, state):
473 if self._window_maximized is not state:
474 self._window_maximized = state
475 self.persistence.save_option(OPTION_WINDOW_MAX, state)
476 return property(**locals())
478 @apply
479 def main_pane_position():
480 doc = ""
481 def fget(self):
482 return self._main_pane_position
483 def fset(self, position):
484 if self._main_pane_position != position:
485 self._main_pane_position = position
486 self.persistence.save_option(OPTION_MAIN_PANE_POS, position)
487 return property(**locals())
489 @apply
490 def sub_pane_position():
491 doc = ""
492 def fget(self):
493 return self._sub_pane_position
494 def fset(self, position):
495 if self._sub_pane_position != position:
496 self._sub_pane_position = position
497 self.persistence.save_option(OPTION_SUB_PANE_POS, position)
498 return property(**locals())
500 @apply
501 def text_magnification():
502 doc = "sets the amount of magnification in the item view"
503 def fget(self):
504 return self._text_magnification
505 def fset(self, tm):
506 if self._text_magnification is not tm:
507 self._text_magnification = tm
508 self.persistence.save_option(OPTION_MAGNIFICATION, tm)
509 return property(**locals())
511 @apply
512 def reload_css():
513 doc = ""
514 def fget(self):
515 return self._reload_css
516 def fset(self, reload_css):
517 self._reload_css = reload_css
518 return property(**locals())
520 def convert_if_necessary(config):
521 if not os.path.exists(config.straw_config_file):
522 return
524 try:
525 f = open(config.straw_config_file, "r")
526 cf = cPickle.load(f)
528 feeds = cf.get('feeds', None)
529 if feeds and feeds[0].get('_n_items_unread', None) is None:
531 def _convert_feeds(feeds, parent):
532 import ItemStore
533 itemstore = ItemStore.get_instance(config.straw_dir)
534 for feed in feeds:
535 if isinstance(feed, list):
536 _convert_feeds(feed[1:], parent)
537 else:
538 stored = feed.get('_items_stored', -1)
539 if stored > -1:
540 cutoff = stored
541 else:
542 cutoff = config.number_of_items_stored
543 feed['_n_items_unread'] = itemstore.get_number_of_unread(feed['_id'], cutoff)
544 return feeds
546 config.feeds = _convert_feeds(feeds, None)
548 if cf.has_key('poll_frequency'):
549 config.poll_frequency = cf.get('poll_frequency')
550 config.number_of_items_stored = cf.get('number_of_items_stored')
551 config.item_order = cf.get('item_order')
552 config.main_window_size = cf.get('main_window_size')
553 config.main_pane_position = cf.get('main_pane_position')
554 config.sub_pane_position = cf.get('sub_pane_position')
555 config.offline = cf.get('offline')
557 del cf['poll_frequency']
558 del cf['number_of_items_stored']
559 del cf['item_order']
560 del cf['main_window_size']
561 del cf['main_pane_position']
562 del cf['sub_pane_position']
563 del cf['offline']
565 f.close()
566 f = open(config.straw_config_file, "w")
567 f.seek(0)
568 f.truncate()
569 cPickle.dump(cf, f, True)
570 finally:
571 f.close()
572 return
574 def create_gconf_persistence():
575 client = gconf.client_get_default()
576 client.add_dir(GCONF_STRAW_ROOT, gconf.CLIENT_PRELOAD_ONELEVEL)
577 return ConfigGConfPersistence(client)
579 def create_pickle_persistence():
580 return ConfigPicklePersistence(Config._straw_config_file)
582 def create_instance():
583 # Add your GConf key here as well!
584 cp = ConfigPersistence(
585 (create_gconf_persistence(),
586 (OPTION_LAST_POLL, OPTION_ITEMS_STORED, OPTION_ITEM_ORDER, OPTION_BROWSER_CMD,
587 OPTION_WINDOW_SIZE_W, OPTION_WINDOW_SIZE_H,
588 OPTION_MAIN_PANE_POS, OPTION_SUB_PANE_POS, OPTION_OFFLINE,
589 OPTION_MAGNIFICATION,
590 OPTION_POLL_FREQUENCY, OPTION_WINDOW_MAX)),
591 (create_pickle_persistence(),
592 (OPTION_FEED_ID_SEQ, OPTION_FEEDS, OPTION_CATEGORIES)))
593 config = Config(cp)
594 config.initialize_proxy()
595 config.initialize_options()
596 convert_if_necessary(config)
597 return config
599 config_instance = None
600 def get_instance():
601 global config_instance
602 if config_instance is None:
603 config_instance = create_instance()
604 return config_instance