alpha: merged httplib2
[straw.git] / src / lib / Config.py
blobc757e7ebf6dd2fb5d101891f8f0fe93b2f9fbfcf
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 from urlparse import urlparse
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 ################
126 # Proxy config
127 class BaseProxyConfig(object):
128 def __init__(self):
129 self._config_working = False
131 @property
132 def config_working(self):
133 return self._config_working
135 @apply
136 def host():
137 doc = ""
138 def fget(self):
139 return self._host
140 def fset(self, host):
141 self._host = host
142 self._ip = None
143 return property(**locals())
145 @apply
146 def ip():
147 doc = ""
148 def fget(self):
149 return self._ip
150 def fset(self, ip):
151 assert self._ip is None, "Overwriting proxy IP not allowed!"
152 if ip is None:
153 self.use = 0
154 self._ip = ip
155 return property(**locals())
157 @property
158 def is_waiting(self):
159 return self.use and not self._ip
161 def _ip_lookup_callback(self, host, ip, data):
162 self.ip = ip
164 class GconfProxyConfig(BaseProxyConfig):
165 """Encapsulate proxy use and location information (host, port, ip),
166 gconf reading and name lookup logic"""
168 GCONF_PROXY_ROOT = "/system/proxy"
169 GCONF_PROXY_MODE = GCONF_PROXY_ROOT + "/mode"
170 GCONF_HTTP_PROXY_ROOT = "/system/http_proxy"
171 GCONF_HTTP_PROXY_HOST = GCONF_HTTP_PROXY_ROOT + "/host"
172 GCONF_HTTP_PROXY_PORT = GCONF_HTTP_PROXY_ROOT + "/port"
173 GCONF_HTTP_PROXY_USE_AUTHENTICATION = GCONF_HTTP_PROXY_ROOT + "/use_authentication"
174 GCONF_HTTP_PROXY_USER = GCONF_HTTP_PROXY_ROOT + "/authentication_user"
175 GCONF_HTTP_PROXY_PASSWORD = GCONF_HTTP_PROXY_ROOT + "/authentication_password"
177 def __init__(self):
178 BaseProxyConfig.__init__(self)
179 client = gconf.client_get_default()
180 self._config_working = client.dir_exists(self.GCONF_PROXY_ROOT) and client.dir_exists(self.GCONF_HTTP_PROXY_ROOT)
181 if not self._config_working:
182 return
183 client.add_dir(self.GCONF_PROXY_ROOT, gconf.CLIENT_PRELOAD_RECURSIVE)
184 client.add_dir(self.GCONF_HTTP_PROXY_ROOT, gconf.CLIENT_PRELOAD_RECURSIVE)
185 client.notify_add(self.GCONF_PROXY_MODE, self.proxy_mode_changed)
186 client.notify_add(self.GCONF_HTTP_PROXY_HOST, self.proxy_host_changed)
187 client.notify_add(self.GCONF_HTTP_PROXY_PORT, self.proxy_port_changed)
188 client.notify_add(self.GCONF_HTTP_PROXY_USE_AUTHENTICATION,
189 self.proxy_auth_changed)
190 client.notify_add(self.GCONF_HTTP_PROXY_USER, self.proxy_auth_changed)
191 client.notify_add(self.GCONF_HTTP_PROXY_PASSWORD, self.proxy_auth_changed)
193 self._ip = None
194 pm = client.get_string(self.GCONF_PROXY_MODE)
195 self.use = pm is not None and pm != "none"
196 self.host = client.get_string(self.GCONF_HTTP_PROXY_HOST)
197 self.port = client.get_int(self.GCONF_HTTP_PROXY_PORT)
198 self.use_authentication = client.get_bool(
199 self.GCONF_HTTP_PROXY_USE_AUTHENTICATION)
200 self.user = client.get_string(self.GCONF_HTTP_PROXY_USER)
201 self.password = client.get_string(self.GCONF_HTTP_PROXY_PASSWORD)
203 # here be gconf logic
204 def proxy_mode_changed(self, client, notify_id, entry, *args):
205 value = entry.value.get_string()
206 self.use = value is not None and value != "none"
208 def proxy_host_changed(self, client, notify_id, entry, *args):
209 value = entry.value.get_string()
210 if value is not None and len(value):
211 self.host = value
212 else:
213 self.use = 0
215 def proxy_port_changed(self, client, notify_id, entry, *args):
216 value = entry.value.get_int()
217 if value is not None and value > 0:
218 self.port = value
219 else:
220 self.use = 0
222 def proxy_auth_changed(self, client, notify_id, entry, *args):
223 value = entry.value
224 if entry.key == self.GCONF_HTTP_PROXY_USE_AUTHENTICATION:
225 if not value:
226 self.use_authentication = 0
227 else:
228 self.use_authentication = 1
229 elif entry.key == self.GCONF_HTTP_PROXY_USER:
230 self.user = value
231 elif entry.key == self.GCONF_HTTP_PROXY_PASSWORD:
232 self.password = value
234 class EnvironmentProxyConfig(BaseProxyConfig):
235 """Encapsulate proxy use and location information, environment reading"""
237 def __init__(self):
238 BaseProxyConfig.__init__(self)
239 try:
240 result = self._read_env()
241 except:
242 result = 0
243 self._config_working = result
244 self.use = self._config_working
246 def _read_env(self):
247 proxy = os.getenv("http_proxy")
248 if not proxy:
249 return 0
250 pparts = urlparse(proxy)
251 proto = pparts[0]
252 host = pparts[1]
253 if proto != "http" or not len(host):
254 return 0
255 hparts = host.split('@')
256 auth = self.user = self.password = self.use_authentication = None
257 if len(hparts) > 2:
258 return 0
259 if len(hparts) == 2:
260 auth, host = hparts
261 if auth:
262 aparts = auth.split(':')
263 if len(aparts) != 2:
264 return 0
265 self.user, self.password = aparts
266 self.use_authentication = 1
267 self.host = host
268 self.port = 80
269 hparts = host.split(':')
270 if len(hparts) > 2:
271 return 0
272 if len(hparts) == 2:
273 self.host = hparts[0]
274 self.port = int(hparts[1])
275 return 1
277 class NullProxyConfig(BaseProxyConfig):
278 def __init__(self):
279 BaseProxyConfig.__init__(self)
280 self.use = 0
281 self._config_working = 0
282 self._host = None
283 self._ip = None
284 self.port = None
285 self.user = None
286 self.password = None
287 self.use_authentication = None
289 ###################
290 # The main man
291 class Config(object, Event.SignalEmitter):
292 _straw_dir = os.path.join(os.getenv("HOME"), ".straw")
293 _straw_config_file = os.path.join(_straw_dir, "config")
295 def __init__(self, persistence):
296 Event.SignalEmitter.__init__(self)
297 self.persistence = persistence
298 self.initialize_slots(Event.ItemOrderChangedSignal,
299 Event.OfflineModeChangedSignal,
300 Event.PollFrequencyChangedSignal,
301 Event.NumberOfItemsStoredChangedSignal)
302 self._feed_id_seq = 0
303 self._poll_freq = 1800
304 self._last_poll = 0
305 self._browser_cmd = ''
306 self._items_stored = 30
307 self._item_order = False
308 self._main_window_size = (640,480)
309 self._main_pane_position = 100
310 self._sub_pane_position = 100
311 self.first_time = None
312 self._offline = True
313 self._window_maximized = False
314 self._reload_css = False
316 def initialize_proxy(self):
317 pcchoices = (GconfProxyConfig, EnvironmentProxyConfig, NullProxyConfig)
318 for p in pcchoices:
319 self._proxy_config = p()
320 if self._proxy_config.config_working:
321 break
323 def initialize_options(self):
324 # Call this after calling Config's constructor
325 self.first_time = ensure_directory(self._straw_dir)
327 if os.path.exists(self.straw_config_file):
328 self._feed_id_seq = self.persistence.load_option(OPTION_FEED_ID_SEQ)
330 self._poll_freq = self.persistence.load_option(OPTION_POLL_FREQUENCY)
331 self._last_poll = self.persistence.load_option(OPTION_LAST_POLL)
332 self._items_stored = self.persistence.load_option(OPTION_ITEMS_STORED)
333 self._browser_cmd = self.persistence.load_option(OPTION_BROWSER_CMD)
334 self._item_order = self.persistence.load_option(OPTION_ITEM_ORDER)
336 width = self.persistence.load_option(OPTION_WINDOW_SIZE_W)
337 height = self.persistence.load_option(OPTION_WINDOW_SIZE_H)
338 if width <= 0:
339 width = 640
340 if height <= 0:
341 height = 480
342 self._main_window_size = (width, height)
344 self._main_pane_position = self.persistence.load_option(
345 OPTION_MAIN_PANE_POS)
346 self._sub_pane_position = self.persistence.load_option(
347 OPTION_SUB_PANE_POS)
348 self._window_maximized = self.persistence.load_option(OPTION_WINDOW_MAX)
349 self._text_magnification = self.persistence.load_option(OPTION_MAGNIFICATION)
350 self._offline = self.persistence.load_option(OPTION_OFFLINE)
352 @property
353 def straw_dir(self):
354 return self._straw_dir
356 @property
357 def straw_config_file(self):
358 return self._straw_config_file
360 @property
361 def proxy_config(self):
362 return self._proxy_config
364 @apply
365 def feeds():
366 doc = "Marshalled feed data"
367 def fget(self):
368 return self.persistence.load_option(OPTION_FEEDS)
369 def fset(self, feeddata):
370 self.persistence.save_option(
371 OPTION_FEEDS, feeddata)
372 return property(**locals())
374 @apply
375 def categories():
376 doc = ""
377 def fget(self):
378 return self.persistence.load_option(OPTION_CATEGORIES)
379 def fset(self, categorydata):
380 self.persistence.save_option(
381 OPTION_CATEGORIES, categorydata)
382 return property(**locals())
384 @apply
385 def poll_frequency():
386 doc = "Polling frequency"
387 def fget(self):
388 return self._poll_freq
389 def fset(self, poll_frequency):
390 if self._poll_freq != poll_frequency:
391 self._poll_freq = poll_frequency
392 self.persistence.save_option(OPTION_POLL_FREQUENCY, poll_frequency)
393 self.emit_signal(Event.PollFrequencyChangedSignal(self, poll_frequency))
394 return property(**locals())
396 @apply
397 def last_poll():
398 doc = "last poll"
399 def fget(self):
400 return self._last_poll
401 def fset(self, last_poll):
402 if self._last_poll != last_poll:
403 self._last_poll = last_poll
404 self.persistence.save_option(OPTION_LAST_POLL, last_poll)
405 return property(**locals())
407 @apply
408 def browser_cmd():
409 doc = "The browser to use"
410 def fget(self):
411 return self._browser_cmd
412 def fset(self, browser_cmd):
413 if self._browser_cmd != browser_cmd:
414 self._browser_cmd = browser_cmd
415 self.persistence.save_option(OPTION_BROWSER_CMD, browser_cmd)
416 return property(**locals())
418 @apply
419 def number_of_items_stored():
420 doc = "Number of items to store per feed"
421 def fget(self):
422 return self._items_stored
423 def fset(self, num=30):
424 if self._items_stored != num:
425 self._items_stored = num
426 self.persistence.save_option(OPTION_ITEMS_STORED, num)
427 self.emit_signal(Event.NumberOfItemsStoredChangedSignal(self))
428 return property(**locals())
430 @apply
431 def item_order():
432 doc = "Ordering of Items"
433 def fget(self):
434 return self._item_order
435 def fset(self, order):
436 if self._item_order != order:
437 self._item_order = order
438 self.persistence.save_option(OPTION_ITEM_ORDER, order)
439 self.emit_signal(Event.ItemOrderChangedSignal(self))
440 return property(**locals())
442 @apply
443 def feed_id_seq():
444 doc = ""
445 def fget(self):
446 return self._feed_id_seq
447 def fset(self, id):
448 self._feed_id_seq = id
449 self.persistence.save_option(OPTION_FEED_ID_SEQ, id)
450 return property(**locals())
452 def next_feed_id_seq(self):
453 self.feed_id_seq += 1
454 return self._feed_id_seq
456 @apply
457 def main_window_size():
458 doc = ""
459 def fget(self):
460 return self._main_window_size
461 def fset(self, size):
462 if self._main_window_size != size:
463 self._main_window_size = size
464 self.persistence.save_option(OPTION_WINDOW_SIZE_W, size[0])
465 self.persistence.save_option(OPTION_WINDOW_SIZE_H, size[1])
466 return property(**locals())
468 @apply
469 def offline():
470 doc = ""
471 def fget(self):
472 return self._offline
473 def fset(self, mode):
474 if self._offline != mode:
475 self._offline = mode
476 self.persistence.save_option(OPTION_OFFLINE, mode)
477 self.emit_signal(Event.OfflineModeChangedSignal(self))
478 return property(**locals())
480 @apply
481 def window_maximized():
482 doc = ""
483 def fget(self):
484 return self._window_maximized
485 def fset(self, state):
486 if self._window_maximized is not state:
487 self._window_maximized = state
488 self.persistence.save_option(OPTION_WINDOW_MAX, state)
489 return property(**locals())
491 @apply
492 def main_pane_position():
493 doc = ""
494 def fget(self):
495 return self._main_pane_position
496 def fset(self, position):
497 if self._main_pane_position != position:
498 self._main_pane_position = position
499 self.persistence.save_option(OPTION_MAIN_PANE_POS, position)
500 return property(**locals())
502 @apply
503 def sub_pane_position():
504 doc = ""
505 def fget(self):
506 return self._sub_pane_position
507 def fset(self, position):
508 if self._sub_pane_position != position:
509 self._sub_pane_position = position
510 self.persistence.save_option(OPTION_SUB_PANE_POS, position)
511 return property(**locals())
513 @apply
514 def text_magnification():
515 doc = "sets the amount of magnification in the item view"
516 def fget(self):
517 return self._text_magnification
518 def fset(self, tm):
519 if self._text_magnification is not tm:
520 self._text_magnification = tm
521 self.persistence.save_option(OPTION_MAGNIFICATION, tm)
522 return property(**locals())
524 @apply
525 def reload_css():
526 doc = ""
527 def fget(self):
528 return self._reload_css
529 def fset(self, reload_css):
530 self._reload_css = reload_css
531 return property(**locals())
533 def convert_if_necessary(config):
534 if not os.path.exists(config.straw_config_file):
535 return
537 try:
538 f = open(config.straw_config_file, "r")
539 cf = cPickle.load(f)
541 feeds = cf.get('feeds', None)
542 if feeds and feeds[0].get('_n_items_unread', None) is None:
544 def _convert_feeds(feeds, parent):
545 import ItemStore
546 itemstore = ItemStore.get_instance(config.straw_dir)
547 for feed in feeds:
548 if isinstance(feed, list):
549 _convert_feeds(feed[1:], parent)
550 else:
551 stored = feed.get('_items_stored', -1)
552 if stored > -1:
553 cutoff = stored
554 else:
555 cutoff = config.number_of_items_stored
556 feed['_n_items_unread'] = itemstore.get_number_of_unread(feed['_id'], cutoff)
557 return feeds
559 config.feeds = _convert_feeds(feeds, None)
561 if cf.has_key('poll_frequency'):
562 config.poll_frequency = cf.get('poll_frequency')
563 config.number_of_items_stored = cf.get('number_of_items_stored')
564 config.item_order = cf.get('item_order')
565 config.main_window_size = cf.get('main_window_size')
566 config.main_pane_position = cf.get('main_pane_position')
567 config.sub_pane_position = cf.get('sub_pane_position')
568 config.offline = cf.get('offline')
570 del cf['poll_frequency']
571 del cf['number_of_items_stored']
572 del cf['item_order']
573 del cf['main_window_size']
574 del cf['main_pane_position']
575 del cf['sub_pane_position']
576 del cf['offline']
578 f.close()
579 f = open(config.straw_config_file, "w")
580 f.seek(0)
581 f.truncate()
582 cPickle.dump(cf, f, True)
583 finally:
584 f.close()
585 return
587 def create_gconf_persistence():
588 client = gconf.client_get_default()
589 client.add_dir(GCONF_STRAW_ROOT, gconf.CLIENT_PRELOAD_ONELEVEL)
590 return ConfigGConfPersistence(client)
592 def create_pickle_persistence():
593 return ConfigPicklePersistence(Config._straw_config_file)
595 def create_instance():
596 # Add your GConf key here as well!
597 cp = ConfigPersistence(
598 (create_gconf_persistence(),
599 (OPTION_LAST_POLL, OPTION_ITEMS_STORED, OPTION_ITEM_ORDER, OPTION_BROWSER_CMD,
600 OPTION_WINDOW_SIZE_W, OPTION_WINDOW_SIZE_H,
601 OPTION_MAIN_PANE_POS, OPTION_SUB_PANE_POS, OPTION_OFFLINE,
602 OPTION_MAGNIFICATION,
603 OPTION_POLL_FREQUENCY, OPTION_WINDOW_MAX)),
604 (create_pickle_persistence(),
605 (OPTION_FEED_ID_SEQ, OPTION_FEEDS, OPTION_CATEGORIES)))
606 config = Config(cp)
607 config.initialize_proxy()
608 config.initialize_options()
609 convert_if_necessary(config)
610 return config
612 config_instance = None
613 def get_instance():
614 global config_instance
615 if config_instance is None:
616 config_instance = create_instance()
617 return config_instance