Fixed exception that occured in some cases when removing nodes.
[straw.git] / straw / Config.py
blob8128845b36d13a4715d9aed63a92cef604cb2eab
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 gobject
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_PANE_LAYOUT = "/ui/pane_layout"
42 OPTION_OFFLINE = "/general/offline"
43 OPTION_POLL_FREQUENCY = "/general/poll_frequency"
44 OPTION_FEED_ID_SEQ = "feed_id_seq"
45 OPTION_FEEDS = "feeds"
46 OPTION_CATEGORIES = "categories"
49 # straw's home directory
50 def straw_home():
51 home = os.getenv('HOME')
52 if not home:
53 home = os.path.expanduser('~')
54 return os.path.join(home, '.straw')
56 def ensure_directory(strawdir):
57 if os.path.exists(strawdir):
58 if not os.path.isdir(strawdir):
59 raise Exception
60 return 0
61 os.mkdir(strawdir)
62 return 1
64 #############
65 # Config persistence classes
66 class ConfigPicklePersistence:
67 def __init__(self, filename):
68 self._config_file = filename
69 self._dict = None
71 def save_option(self, option, value):
72 if self._dict is None:
73 self._initialize_dict()
74 temp_config_file = self._config_file + ".working"
75 pickle_file = open(temp_config_file, "w")
76 self._dict[option] = value
77 cPickle.dump(self._dict, pickle_file, True)
78 pickle_file.close()
79 os.rename(temp_config_file, self._config_file)
80 return
82 def load_option(self, option):
83 if self._dict is None:
84 self._initialize_dict()
85 return self._dict.get(option, None)
87 def _initialize_dict(self):
88 if os.path.exists(self._config_file):
89 pickle_file = open(self._config_file, "r")
90 self._dict = cPickle.load(pickle_file)
91 pickle_file.close()
92 else:
93 self._dict = {}
95 class ConfigGConfPersistence:
96 SAVERS = {OPTION_LAST_POLL: 'int',
97 OPTION_ITEMS_STORED: 'int',
98 OPTION_ITEM_ORDER: 'bool',
99 OPTION_BROWSER_CMD: 'string',
100 OPTION_WINDOW_SIZE_W: 'int',
101 OPTION_WINDOW_SIZE_H: 'int',
102 OPTION_MAIN_PANE_POS: 'int',
103 OPTION_SUB_PANE_POS: 'int',
104 OPTION_OFFLINE: 'bool',
105 OPTION_WINDOW_MAX: 'bool',
106 OPTION_MAGNIFICATION: 'float',
107 OPTION_POLL_FREQUENCY: 'int',
108 OPTION_PANE_LAYOUT: 'string',
111 def __init__(self, client):
112 self.client = client
114 def save_option(self, option, value):
115 getattr(self.client, 'set_' + self.SAVERS[option])(
116 GCONF_STRAW_ROOT + option, value)
118 def load_option(self, option):
119 return getattr(self.client, 'get_' + self.SAVERS[option])(
120 GCONF_STRAW_ROOT + option)
122 class ConfigPersistence:
123 def __init__(self, *backends):
124 self.backends = backends
126 def save_option(self, option, value):
127 for b in self.backends:
128 if option in b[1]:
129 b[0].save_option(option, value)
131 def load_option(self, option):
132 for b in self.backends:
133 if option in b[1]:
134 return b[0].load_option(option)
136 class ProxyConfig(object):
137 def __init__(self):
138 self._active = False
139 self._host = None
140 self._port = None
141 self._auth = None
142 self._username = None
143 self._password = None
145 @apply
146 def active():
147 doc = "this will be true when one of the subclasses is used"
148 def fget(self):
149 return self._active
150 def fset(self, active):
151 self._active = active
152 return property(**locals())
154 @apply
155 def host():
156 doc = "the host name where the proxy server lives"
157 def fget(self):
158 return self._host
159 def fset(self, host):
160 self._host = host
161 return property(**locals())
163 @apply
164 def port():
165 doc = "the port of the proxy server to connect to"
166 def fget(self):
167 return self._port
168 def fset(self, port):
169 self._port = port
170 return property(**locals())
172 @apply
173 def auth():
174 doc = "true if we need to authenticate to the proxy server"
175 def fget(self):
176 return self._auth
177 def fset(self, isauth):
178 self._auth = isauth
179 return property(**locals())
181 @apply
182 def username():
183 doc = "the username to use when authenticating to the proxy"
184 def fget(self):
185 return self._username
186 def fset(self, username):
187 self._username = username
188 return property(**locals())
190 @apply
191 def password():
192 doc = "the password to use when authenticating to the proxy"
193 def fget(self):
194 return self._password
195 def fset(self, password):
196 self._password = password
197 return property(**locals())
200 class GconfProxyConfig(ProxyConfig):
201 """Encapsulate proxy use and location information (host, port, ip),
202 gconf reading and name lookup logic"""
204 GCONF_HTTP_PROXY = "/system/http_proxy"
205 GCONF_HTTP_PROXY_USE = GCONF_HTTP_PROXY + "/use_http_proxy"
206 GCONF_HTTP_PROXY_HOST = GCONF_HTTP_PROXY + "/host"
207 GCONF_HTTP_PROXY_PORT = GCONF_HTTP_PROXY + "/port"
208 GCONF_HTTP_PROXY_AUTHENTICATION = GCONF_HTTP_PROXY + "/use_authentication"
209 GCONF_HTTP_PROXY_USER = GCONF_HTTP_PROXY + "/authentication_user"
210 GCONF_HTTP_PROXY_PASSWORD = GCONF_HTTP_PROXY + "/authentication_password"
212 def __init__(self):
213 ProxyConfig.__init__(self)
214 client = gconf.client_get_default()
215 self.active = client.dir_exists(self.GCONF_HTTP_PROXY)
216 if not self.active:
217 return
219 client.add_dir(self.GCONF_HTTP_PROXY, gconf.CLIENT_PRELOAD_RECURSIVE)
220 client.notify_add(self.GCONF_HTTP_PROXY_USE, self.proxy_mode_changed)
221 client.notify_add(self.GCONF_HTTP_PROXY_HOST, self.proxy_host_changed)
222 client.notify_add(self.GCONF_HTTP_PROXY_PORT, self.proxy_port_changed)
223 client.notify_add(self.GCONF_HTTP_PROXY_AUTHENTICATION, self.proxy_auth_changed)
224 client.notify_add(self.GCONF_HTTP_PROXY_USER, self.proxy_auth_username_changed)
225 client.notify_add(self.GCONF_HTTP_PROXY_PASSWORD, self.proxy_auth_password_changed)
227 self.active = client.get_bool(self.GCONF_HTTP_PROXY_USE)
228 self.host = client.get_string(self.GCONF_HTTP_PROXY_HOST)
229 self.port = client.get_int(self.GCONF_HTTP_PROXY_PORT)
230 self.auth = client.get_bool(self.GCONF_HTTP_PROXY_AUTHENTICATION)
231 if self.auth:
232 self.username = client.get_string(self.GCONF_HTTP_PROXY_USER)
233 self.password = client.get_string(self.GCONF_HTTP_PROXY_PASSWORD)
236 # here be gconf logic
237 def proxy_mode_changed(self, client, notify_id, entry, *args):
238 self.active = entry.value.get_bool()
240 def proxy_host_changed(self, client, notify_id, entry, *args):
241 value = entry.value.get_string()
242 if value:
243 self.host = value
244 else:
245 self.active = False
247 def proxy_port_changed(self, client, notify_id, entry, *args):
248 value = entry.value.get_int()
249 if value:
250 self.port = value
251 else:
252 self.active = False
254 def proxy_auth_changed(self, client, notify_id, entry, *args):
255 value = entry.value.get_bool()
256 if value:
257 self.auth = value
259 def proxy_auth_username_changed(self, client, notify_id, entry, *args):
260 value = entry.value.get_string()
261 self.username = value
263 def proxy_auth_password_changed(self, client, notify_id, entry, *args):
264 value = entry.value.get_string()
265 self.password = value
267 class EnvironmentProxyConfig(ProxyConfig):
268 """Encapsulate proxy use and location information, environment reading"""
270 def __init__(self):
271 ProxyConfig.__init__(self)
272 self._read_env()
274 def _read_env(self):
275 proxy = None
276 proxies = urllib.getproxies()
277 if not proxies:
278 return
280 for key, value in proxies.iteritems():
281 proxy = proxies.get(key)
282 user, authority = urllib.splituser(proxy)
283 if authority:
284 self.host, self.port = urllib.splitport(authority)
285 self.active = True
286 if user:
287 self.username, self.password = urllib.splitpasswd(user)
288 self.auth = True
289 return
291 class Config(gobject.GObject):
292 _straw_config_file = os.path.join(straw_home(), "config")
294 __gsignals__ = {
295 'item-order-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
296 'offline-mode-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
297 'refresh-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
298 'item-stored-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
301 def __init__(self, persistence):
302 gobject.GObject.__init__(self)
303 self.persistence = persistence
304 self._feed_id_seq = 0
305 self._poll_freq = 1800
306 self._last_poll = 0
307 self._browser_cmd = ''
308 self._items_stored = 30
309 self._item_order = True
310 self._main_window_size = (640,480)
311 self._main_pane_position = 100
312 self._sub_pane_position = 100
313 self.first_time = None
314 self._offline = True
315 self._window_maximized = False
316 self._reload_css = False
317 self._proxy = None
318 self._pane_layout = 'vertical'
320 def initialize_proxy(self):
321 # EnvironmentProxy has precedence
322 self._proxy = EnvironmentProxyConfig()
323 if not self._proxy.active:
324 # .. defaults to GConfProxyConfig so we can listen for network
325 # setting changes (e.g, changes in network preference)
326 self._proxy = GconfProxyConfig()
328 def initialize_options(self):
329 # Call this after calling Config's constructor
330 self.first_time = ensure_directory(straw_home())
332 if os.path.exists(self.straw_config_file):
333 self._feed_id_seq = self.persistence.load_option(OPTION_FEED_ID_SEQ)
335 self._poll_freq = self.persistence.load_option(OPTION_POLL_FREQUENCY)
336 self._last_poll = self.persistence.load_option(OPTION_LAST_POLL)
337 self._items_stored = self.persistence.load_option(OPTION_ITEMS_STORED)
338 self._browser_cmd = self.persistence.load_option(OPTION_BROWSER_CMD)
339 self._item_order = self.persistence.load_option(OPTION_ITEM_ORDER)
341 if not self._poll_freq:
342 self.poll_frequency = 1800
343 if not self._items_stored:
344 self.number_of_items_stored = 30
345 if not self._item_order:
346 self.item_order = True
348 width = self.persistence.load_option(OPTION_WINDOW_SIZE_W)
349 height = self.persistence.load_option(OPTION_WINDOW_SIZE_H)
350 if not width:
351 width = 640
352 if not height:
353 height = 480
354 self._main_window_size = (width, height)
356 self._main_pane_position = self.persistence.load_option(OPTION_MAIN_PANE_POS)
357 self._sub_pane_position = self.persistence.load_option(OPTION_SUB_PANE_POS)
358 if not self._main_pane_position:
359 self._main_pane_position = 100
360 if not self._sub_pane_position:
361 self._sub_pane_position = 100
362 print (self.main_pane_position, self.sub_pane_position)
364 self._window_maximized = self.persistence.load_option(OPTION_WINDOW_MAX)
365 self._text_magnification = self.persistence.load_option(OPTION_MAGNIFICATION)
366 self._offline = self.persistence.load_option(OPTION_OFFLINE)
367 self._pane_layout = self.persistence.load_option(OPTION_PANE_LAYOUT)
369 @property
370 def straw_config_file(self):
371 return self._straw_config_file
373 @property
374 def proxy(self):
375 return self._proxy
377 @apply
378 def feeds():
379 doc = "Marshalled feed data"
380 def fget(self):
381 return self.persistence.load_option(OPTION_FEEDS)
382 def fset(self, feeddata):
383 self.persistence.save_option(OPTION_FEEDS, feeddata)
384 return property(**locals())
386 @apply
387 def categories():
388 doc = ""
389 def fget(self):
390 return self.persistence.load_option(OPTION_CATEGORIES)
391 def fset(self, categorydata):
392 self.persistence.save_option(
393 OPTION_CATEGORIES, categorydata)
394 return property(**locals())
396 @apply
397 def poll_frequency():
398 doc = "Polling frequency"
399 def fget(self):
400 return self._poll_freq
401 def fset(self, poll_frequency):
402 if self._poll_freq != poll_frequency:
403 self._poll_freq = poll_frequency
404 self.persistence.save_option(OPTION_POLL_FREQUENCY, poll_frequency)
405 self.emit('refresh-changed')
406 return property(**locals())
408 @apply
409 def last_poll():
410 doc = "last poll"
411 def fget(self):
412 return self._last_poll
413 def fset(self, last_poll):
414 if self._last_poll != last_poll:
415 self._last_poll = last_poll
416 self.persistence.save_option(OPTION_LAST_POLL, last_poll)
417 return property(**locals())
419 @apply
420 def browser_cmd():
421 doc = "The browser to use"
422 def fget(self):
423 return self._browser_cmd
424 def fset(self, browser_cmd):
425 if self._browser_cmd != browser_cmd:
426 self._browser_cmd = browser_cmd
427 self.persistence.save_option(OPTION_BROWSER_CMD, browser_cmd)
428 return property(**locals())
430 @apply
431 def number_of_items_stored():
432 doc = "Number of items to store per feed"
433 def fget(self):
434 return self._items_stored
435 def fset(self, num=30):
436 if self._items_stored != num:
437 self._items_stored = num
438 self.persistence.save_option(OPTION_ITEMS_STORED, num)
439 self.emit('item-stored-changed')
440 return property(**locals())
442 @apply
443 def item_order():
444 doc = "Ordering of Items"
445 def fget(self):
446 return self._item_order
447 def fset(self, order):
448 if self._item_order != order:
449 self._item_order = order
450 self.persistence.save_option(OPTION_ITEM_ORDER, order)
451 self.emit('item-order-changed')
452 return property(**locals())
454 @apply
455 def feed_id_seq():
456 doc = ""
457 def fget(self):
458 return self._feed_id_seq
459 def fset(self, id):
460 self._feed_id_seq = id
461 self.persistence.save_option(OPTION_FEED_ID_SEQ, id)
462 return property(**locals())
464 def next_feed_id_seq(self):
465 self.feed_id_seq += 1
466 return self._feed_id_seq
468 @apply
469 def main_window_size():
470 doc = ""
471 def fget(self):
472 return self._main_window_size
473 def fset(self, size):
474 if self._main_window_size != size:
475 self._main_window_size = size
476 self.persistence.save_option(OPTION_WINDOW_SIZE_W, size[0])
477 self.persistence.save_option(OPTION_WINDOW_SIZE_H, size[1])
478 return property(**locals())
480 @apply
481 def offline():
482 doc = ""
483 def fget(self):
484 return self._offline
485 def fset(self, mode):
486 if self._offline != mode:
487 self._offline = mode
488 self.persistence.save_option(OPTION_OFFLINE, mode)
489 self.emit('offline-mode-changed')
490 return property(**locals())
492 @apply
493 def window_maximized():
494 doc = ""
495 def fget(self):
496 return self._window_maximized
497 def fset(self, state):
498 if self._window_maximized is not state:
499 self._window_maximized = state
500 self.persistence.save_option(OPTION_WINDOW_MAX, state)
501 return property(**locals())
503 @apply
504 def main_pane_position():
505 doc = ""
506 def fget(self):
507 return self._main_pane_position
508 def fset(self, position):
509 if self._main_pane_position != position:
510 self._main_pane_position = position
511 self.persistence.save_option(OPTION_MAIN_PANE_POS, position)
512 return property(**locals())
514 @apply
515 def sub_pane_position():
516 doc = ""
517 def fget(self):
518 return self._sub_pane_position
519 def fset(self, position):
520 if self._sub_pane_position != position:
521 self._sub_pane_position = position
522 self.persistence.save_option(OPTION_SUB_PANE_POS, position)
523 return property(**locals())
525 @apply
526 def pane_layout():
527 doc = "The pane layout to use"
528 def fget(self):
529 return self._pane_layout
530 def fset(self, pane_layout):
531 if self._pane_layout != pane_layout:
532 self._pane_layout = pane_layout
533 self.persistence.save_option(OPTION_PANE_LAYOUT, pane_layout)
534 return property(**locals())
536 @apply
537 def text_magnification():
538 doc = "sets the amount of magnification in the item view"
539 def fget(self):
540 return self._text_magnification
541 def fset(self, tm):
542 if self._text_magnification is not tm:
543 self._text_magnification = tm
544 self.persistence.save_option(OPTION_MAGNIFICATION, tm)
545 return property(**locals())
547 @apply
548 def reload_css():
549 doc = ""
550 def fget(self):
551 return self._reload_css
552 def fset(self, reload_css):
553 self._reload_css = reload_css
554 return property(**locals())
556 def convert_if_necessary(config):
557 if not os.path.exists(config.straw_config_file):
558 return
560 try:
561 f = open(config.straw_config_file, "r")
562 cf = cPickle.load(f)
564 if cf.has_key('poll_frequency'):
565 config.poll_frequency = cf.get('poll_frequency')
566 config.number_of_items_stored = cf.get('number_of_items_stored')
567 config.item_order = cf.get('item_order')
568 config.main_window_size = cf.get('main_window_size')
569 config.main_pane_position = cf.get('main_pane_position')
570 config.sub_pane_position = cf.get('sub_pane_position')
571 config.offline = cf.get('offline')
573 del cf['poll_frequency']
574 del cf['number_of_items_stored']
575 del cf['item_order']
576 del cf['main_window_size']
577 del cf['main_pane_position']
578 del cf['sub_pane_position']
579 del cf['offline']
581 f.close()
582 f = open(config.straw_config_file, "w")
583 f.seek(0)
584 f.truncate()
585 cPickle.dump(cf, f, True)
586 finally:
587 f.close()
588 return
590 def create_gconf_persistence():
591 client = gconf.client_get_default()
592 client.add_dir(GCONF_STRAW_ROOT, gconf.CLIENT_PRELOAD_ONELEVEL)
593 return ConfigGConfPersistence(client)
595 def create_pickle_persistence():
596 return ConfigPicklePersistence(Config._straw_config_file)
598 def create_instance():
599 # Add your GConf key here as well!
600 cp = ConfigPersistence(
601 (create_gconf_persistence(),
602 (OPTION_LAST_POLL, OPTION_ITEMS_STORED, OPTION_ITEM_ORDER, OPTION_BROWSER_CMD,
603 OPTION_WINDOW_SIZE_W, OPTION_WINDOW_SIZE_H,
604 OPTION_MAIN_PANE_POS, OPTION_SUB_PANE_POS, OPTION_OFFLINE,
605 OPTION_MAGNIFICATION,
606 OPTION_POLL_FREQUENCY, OPTION_WINDOW_MAX, OPTION_PANE_LAYOUT)),
607 (create_pickle_persistence(),
608 (OPTION_FEED_ID_SEQ, OPTION_FEEDS, OPTION_CATEGORIES)))
609 config = Config(cp)
610 config.initialize_proxy()
611 config.initialize_options()
612 convert_if_necessary(config)
613 return config
615 config_instance = None
616 def get_instance():
617 global config_instance
618 if not config_instance:
619 config_instance = create_instance()
620 return config_instance