feed list view and feed/category event handling fixes
[straw.git] / src / lib / Config.py
blob2c23caca556bfe6be41bef9143973679f45acabd
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"
48 # straw's home directory
49 def straw_home():
50 home = os.getenv('HOME')
51 if not home:
52 home = os.path.expanduser('~')
53 return os.path.join(home, '.straw')
55 def ensure_directory(strawdir):
56 if os.path.exists(strawdir):
57 if not os.path.isdir(strawdir):
58 raise Exception
59 return 0
60 os.mkdir(strawdir)
61 return 1
63 #############
64 # Config persistence classes
65 class ConfigPicklePersistence:
66 def __init__(self, filename):
67 self._config_file = filename
68 self._dict = None
70 def save_option(self, option, value):
71 if self._dict is None:
72 self._initialize_dict()
73 temp_config_file = self._config_file + ".working"
74 pickle_file = open(temp_config_file, "w")
75 self._dict[option] = value
76 cPickle.dump(self._dict, pickle_file, True)
77 pickle_file.close()
78 os.rename(temp_config_file, self._config_file)
79 return
81 def load_option(self, option):
82 if self._dict is None:
83 self._initialize_dict()
84 return self._dict.get(option, None)
86 def _initialize_dict(self):
87 if os.path.exists(self._config_file):
88 pickle_file = open(self._config_file, "r")
89 self._dict = cPickle.load(pickle_file)
90 pickle_file.close()
91 else:
92 self._dict = {}
94 class ConfigGConfPersistence:
95 SAVERS = {OPTION_LAST_POLL: 'int',
96 OPTION_ITEMS_STORED: 'int',
97 OPTION_ITEM_ORDER: 'bool',
98 OPTION_BROWSER_CMD: 'string',
99 OPTION_WINDOW_SIZE_W: 'int',
100 OPTION_WINDOW_SIZE_H: 'int',
101 OPTION_MAIN_PANE_POS: 'int',
102 OPTION_SUB_PANE_POS: 'int',
103 OPTION_OFFLINE: 'bool',
104 OPTION_WINDOW_MAX: 'bool',
105 OPTION_MAGNIFICATION: 'float',
106 OPTION_POLL_FREQUENCY: 'int'}
108 def __init__(self, client):
109 self.client = client
111 def save_option(self, option, value):
112 getattr(self.client, 'set_' + self.SAVERS[option])(
113 GCONF_STRAW_ROOT + option, value)
115 def load_option(self, option):
116 return getattr(self.client, 'get_' + self.SAVERS[option])(
117 GCONF_STRAW_ROOT + option)
119 class ConfigPersistence:
120 def __init__(self, *backends):
121 self.backends = backends
123 def save_option(self, option, value):
124 for b in self.backends:
125 if option in b[1]:
126 b[0].save_option(option, value)
128 def load_option(self, option):
129 for b in self.backends:
130 if option in b[1]:
131 return b[0].load_option(option)
133 class ProxyConfig(object):
134 def __init__(self):
135 self._active = False
136 self._host = None
137 self._port = None
138 self._auth = None
139 self._username = None
140 self._password = None
142 @apply
143 def active():
144 doc = "this will be true when one of the subclasses is used"
145 def fget(self):
146 return self._active
147 def fset(self, active):
148 self._active = active
149 return property(**locals())
151 @apply
152 def host():
153 doc = "the host name where the proxy server lives"
154 def fget(self):
155 return self._host
156 def fset(self, host):
157 self._host = host
158 return property(**locals())
160 @apply
161 def port():
162 doc = "the port of the proxy server to connect to"
163 def fget(self):
164 return self._port
165 def fset(self, port):
166 self._port = port
167 return property(**locals())
169 @apply
170 def auth():
171 doc = "true if we need to authenticate to the proxy server"
172 def fget(self):
173 return self._auth
174 def fset(self, isauth):
175 self._auth = isauth
176 return property(**locals())
178 @apply
179 def username():
180 doc = "the username to use when authenticating to the proxy"
181 def fget(self):
182 return self._username
183 def fset(self, username):
184 self._username = username
185 return property(**locals())
187 @apply
188 def password():
189 doc = "the password to use when authenticating to the proxy"
190 def fget(self):
191 return self._password
192 def fset(self, password):
193 self._password = password
194 return property(**locals())
197 class GconfProxyConfig(ProxyConfig):
198 """Encapsulate proxy use and location information (host, port, ip),
199 gconf reading and name lookup logic"""
201 GCONF_HTTP_PROXY = "/system/http_proxy"
202 GCONF_HTTP_PROXY_USE = GCONF_HTTP_PROXY + "/use_http_proxy"
203 GCONF_HTTP_PROXY_HOST = GCONF_HTTP_PROXY + "/host"
204 GCONF_HTTP_PROXY_PORT = GCONF_HTTP_PROXY + "/port"
205 GCONF_HTTP_PROXY_AUTHENTICATION = GCONF_HTTP_PROXY + "/use_authentication"
206 GCONF_HTTP_PROXY_USER = GCONF_HTTP_PROXY + "/authentication_user"
207 GCONF_HTTP_PROXY_PASSWORD = GCONF_HTTP_PROXY + "/authentication_password"
209 def __init__(self):
210 ProxyConfig.__init__(self)
211 client = gconf.client_get_default()
212 self.active = client.dir_exists(self.GCONF_HTTP_PROXY)
213 if not self.active:
214 return
216 client.add_dir(self.GCONF_HTTP_PROXY, gconf.CLIENT_PRELOAD_RECURSIVE)
217 client.notify_add(self.GCONF_HTTP_PROXY_USE, self.proxy_mode_changed)
218 client.notify_add(self.GCONF_HTTP_PROXY_HOST, self.proxy_host_changed)
219 client.notify_add(self.GCONF_HTTP_PROXY_PORT, self.proxy_port_changed)
220 client.notify_add(self.GCONF_HTTP_PROXY_AUTHENTICATION, self.proxy_auth_changed)
221 client.notify_add(self.GCONF_HTTP_PROXY_USER, self.proxy_auth_username_changed)
222 client.notify_add(self.GCONF_HTTP_PROXY_PASSWORD, self.proxy_auth_password_changed)
224 self.active = client.get_bool(self.GCONF_HTTP_PROXY_USE)
225 self.host = client.get_string(self.GCONF_HTTP_PROXY_HOST)
226 self.port = client.get_int(self.GCONF_HTTP_PROXY_PORT)
227 self.auth = client.get_bool(self.GCONF_HTTP_PROXY_AUTHENTICATION)
228 if self.auth:
229 self.username = client.get_string(self.GCONF_HTTP_PROXY_USER)
230 self.password = client.get_string(self.GCONF_HTTP_PROXY_PASSWORD)
233 # here be gconf logic
234 def proxy_mode_changed(self, client, notify_id, entry, *args):
235 self.active = entry.value.get_bool()
237 def proxy_host_changed(self, client, notify_id, entry, *args):
238 value = entry.value.get_string()
239 if value:
240 self.host = value
241 else:
242 self.active = False
244 def proxy_port_changed(self, client, notify_id, entry, *args):
245 value = entry.value.get_int()
246 if value:
247 self.port = value
248 else:
249 self.active = False
251 def proxy_auth_changed(self, client, notify_id, entry, *args):
252 value = entry.value.get_bool()
253 if value:
254 self.auth = value
256 def proxy_auth_username_changed(self, client, notify_id, entry, *args):
257 value = entry.value.get_string()
258 self.username = value
260 def proxy_auth_password_changed(self, client, notify_id, entry, *args):
261 value = entry.value.get_string()
262 self.password = value
264 class EnvironmentProxyConfig(ProxyConfig):
265 """Encapsulate proxy use and location information, environment reading"""
267 def __init__(self):
268 ProxyConfig.__init__(self)
269 self._read_env()
271 def _read_env(self):
272 proxy = None
273 proxies = urllib.getproxies()
274 if not proxies:
275 return
277 for key, value in proxies.iteritems():
278 proxy = proxies.get(key)
279 user, authority = urllib.splituser(proxy)
280 if authority:
281 self.host, self.port = urllib.splitport(authority)
282 self.active = True
283 if user:
284 self.username, self.password = urllib.splitpasswd(user)
285 self.auth = True
286 return
288 class Config(object, Event.SignalEmitter):
289 _straw_config_file = os.path.join(straw_home(), "config")
291 def __init__(self, persistence):
292 Event.SignalEmitter.__init__(self)
293 self.persistence = persistence
294 self.initialize_slots(Event.ItemOrderChangedSignal,
295 Event.OfflineModeChangedSignal,
296 Event.PollFrequencyChangedSignal,
297 Event.NumberOfItemsStoredChangedSignal)
298 self._feed_id_seq = 0
299 self._poll_freq = 1800
300 self._last_poll = 0
301 self._browser_cmd = ''
302 self._items_stored = 30
303 self._item_order = False
304 self._main_window_size = (640,480)
305 self._main_pane_position = 100
306 self._sub_pane_position = 100
307 self.first_time = None
308 self._offline = True
309 self._window_maximized = False
310 self._reload_css = False
311 self._proxy = None
313 def initialize_proxy(self):
314 # EnvironmentProxy has precedence
315 self._proxy = EnvironmentProxyConfig()
316 if not self._proxy.active:
317 # .. default to GConfProxyConfig so we can listen for network
318 # setting changes (e.g, changes in network preference)
319 self._proxy = GconfProxyConfig()
321 def initialize_options(self):
322 # Call this after calling Config's constructor
323 self.first_time = ensure_directory(straw_home())
325 if os.path.exists(self.straw_config_file):
326 self._feed_id_seq = self.persistence.load_option(OPTION_FEED_ID_SEQ)
328 self._poll_freq = self.persistence.load_option(OPTION_POLL_FREQUENCY)
329 self._last_poll = self.persistence.load_option(OPTION_LAST_POLL)
330 self._items_stored = self.persistence.load_option(OPTION_ITEMS_STORED)
331 self._browser_cmd = self.persistence.load_option(OPTION_BROWSER_CMD)
332 self._item_order = self.persistence.load_option(OPTION_ITEM_ORDER)
334 width = self.persistence.load_option(OPTION_WINDOW_SIZE_W)
335 height = self.persistence.load_option(OPTION_WINDOW_SIZE_H)
336 if width <= 0:
337 width = 640
338 if height <= 0:
339 height = 480
340 self._main_window_size = (width, height)
342 self._main_pane_position = self.persistence.load_option(
343 OPTION_MAIN_PANE_POS)
344 self._sub_pane_position = self.persistence.load_option(
345 OPTION_SUB_PANE_POS)
346 self._window_maximized = self.persistence.load_option(OPTION_WINDOW_MAX)
347 self._text_magnification = self.persistence.load_option(OPTION_MAGNIFICATION)
348 self._offline = self.persistence.load_option(OPTION_OFFLINE)
350 @property
351 def straw_config_file(self):
352 return self._straw_config_file
354 @property
355 def proxy(self):
356 return self._proxy
358 @apply
359 def feeds():
360 doc = "Marshalled feed data"
361 def fget(self):
362 return self.persistence.load_option(OPTION_FEEDS)
363 def fset(self, feeddata):
364 self.persistence.save_option(
365 OPTION_FEEDS, feeddata)
366 return property(**locals())
368 @apply
369 def categories():
370 doc = ""
371 def fget(self):
372 return self.persistence.load_option(OPTION_CATEGORIES)
373 def fset(self, categorydata):
374 self.persistence.save_option(
375 OPTION_CATEGORIES, categorydata)
376 return property(**locals())
378 @apply
379 def poll_frequency():
380 doc = "Polling frequency"
381 def fget(self):
382 return self._poll_freq
383 def fset(self, poll_frequency):
384 if self._poll_freq != poll_frequency:
385 self._poll_freq = poll_frequency
386 self.persistence.save_option(OPTION_POLL_FREQUENCY, poll_frequency)
387 self.emit_signal(Event.PollFrequencyChangedSignal(self, poll_frequency))
388 return property(**locals())
390 @apply
391 def last_poll():
392 doc = "last poll"
393 def fget(self):
394 return self._last_poll
395 def fset(self, last_poll):
396 if self._last_poll != last_poll:
397 self._last_poll = last_poll
398 self.persistence.save_option(OPTION_LAST_POLL, last_poll)
399 return property(**locals())
401 @apply
402 def browser_cmd():
403 doc = "The browser to use"
404 def fget(self):
405 return self._browser_cmd
406 def fset(self, browser_cmd):
407 if self._browser_cmd != browser_cmd:
408 self._browser_cmd = browser_cmd
409 self.persistence.save_option(OPTION_BROWSER_CMD, browser_cmd)
410 return property(**locals())
412 @apply
413 def number_of_items_stored():
414 doc = "Number of items to store per feed"
415 def fget(self):
416 return self._items_stored
417 def fset(self, num=30):
418 if self._items_stored != num:
419 self._items_stored = num
420 self.persistence.save_option(OPTION_ITEMS_STORED, num)
421 self.emit_signal(Event.NumberOfItemsStoredChangedSignal(self))
422 return property(**locals())
424 @apply
425 def item_order():
426 doc = "Ordering of Items"
427 def fget(self):
428 return self._item_order
429 def fset(self, order):
430 if self._item_order != order:
431 self._item_order = order
432 self.persistence.save_option(OPTION_ITEM_ORDER, order)
433 self.emit_signal(Event.ItemOrderChangedSignal(self))
434 return property(**locals())
436 @apply
437 def feed_id_seq():
438 doc = ""
439 def fget(self):
440 return self._feed_id_seq
441 def fset(self, id):
442 self._feed_id_seq = id
443 self.persistence.save_option(OPTION_FEED_ID_SEQ, id)
444 return property(**locals())
446 def next_feed_id_seq(self):
447 self.feed_id_seq += 1
448 return self._feed_id_seq
450 @apply
451 def main_window_size():
452 doc = ""
453 def fget(self):
454 return self._main_window_size
455 def fset(self, size):
456 if self._main_window_size != size:
457 self._main_window_size = size
458 self.persistence.save_option(OPTION_WINDOW_SIZE_W, size[0])
459 self.persistence.save_option(OPTION_WINDOW_SIZE_H, size[1])
460 return property(**locals())
462 @apply
463 def offline():
464 doc = ""
465 def fget(self):
466 return self._offline
467 def fset(self, mode):
468 if self._offline != mode:
469 self._offline = mode
470 self.persistence.save_option(OPTION_OFFLINE, mode)
471 self.emit_signal(Event.OfflineModeChangedSignal(self))
472 return property(**locals())
474 @apply
475 def window_maximized():
476 doc = ""
477 def fget(self):
478 return self._window_maximized
479 def fset(self, state):
480 if self._window_maximized is not state:
481 self._window_maximized = state
482 self.persistence.save_option(OPTION_WINDOW_MAX, state)
483 return property(**locals())
485 @apply
486 def main_pane_position():
487 doc = ""
488 def fget(self):
489 return self._main_pane_position
490 def fset(self, position):
491 if self._main_pane_position != position:
492 self._main_pane_position = position
493 self.persistence.save_option(OPTION_MAIN_PANE_POS, position)
494 return property(**locals())
496 @apply
497 def sub_pane_position():
498 doc = ""
499 def fget(self):
500 return self._sub_pane_position
501 def fset(self, position):
502 if self._sub_pane_position != position:
503 self._sub_pane_position = position
504 self.persistence.save_option(OPTION_SUB_PANE_POS, position)
505 return property(**locals())
507 @apply
508 def text_magnification():
509 doc = "sets the amount of magnification in the item view"
510 def fget(self):
511 return self._text_magnification
512 def fset(self, tm):
513 if self._text_magnification is not tm:
514 self._text_magnification = tm
515 self.persistence.save_option(OPTION_MAGNIFICATION, tm)
516 return property(**locals())
518 @apply
519 def reload_css():
520 doc = ""
521 def fget(self):
522 return self._reload_css
523 def fset(self, reload_css):
524 self._reload_css = reload_css
525 return property(**locals())
527 def convert_if_necessary(config):
528 if not os.path.exists(config.straw_config_file):
529 return
531 try:
532 f = open(config.straw_config_file, "r")
533 cf = cPickle.load(f)
535 if cf.has_key('poll_frequency'):
536 config.poll_frequency = cf.get('poll_frequency')
537 config.number_of_items_stored = cf.get('number_of_items_stored')
538 config.item_order = cf.get('item_order')
539 config.main_window_size = cf.get('main_window_size')
540 config.main_pane_position = cf.get('main_pane_position')
541 config.sub_pane_position = cf.get('sub_pane_position')
542 config.offline = cf.get('offline')
544 del cf['poll_frequency']
545 del cf['number_of_items_stored']
546 del cf['item_order']
547 del cf['main_window_size']
548 del cf['main_pane_position']
549 del cf['sub_pane_position']
550 del cf['offline']
552 f.close()
553 f = open(config.straw_config_file, "w")
554 f.seek(0)
555 f.truncate()
556 cPickle.dump(cf, f, True)
557 finally:
558 f.close()
559 return
561 def create_gconf_persistence():
562 client = gconf.client_get_default()
563 client.add_dir(GCONF_STRAW_ROOT, gconf.CLIENT_PRELOAD_ONELEVEL)
564 return ConfigGConfPersistence(client)
566 def create_pickle_persistence():
567 return ConfigPicklePersistence(Config._straw_config_file)
569 def create_instance():
570 # Add your GConf key here as well!
571 cp = ConfigPersistence(
572 (create_gconf_persistence(),
573 (OPTION_LAST_POLL, OPTION_ITEMS_STORED, OPTION_ITEM_ORDER, OPTION_BROWSER_CMD,
574 OPTION_WINDOW_SIZE_W, OPTION_WINDOW_SIZE_H,
575 OPTION_MAIN_PANE_POS, OPTION_SUB_PANE_POS, OPTION_OFFLINE,
576 OPTION_MAGNIFICATION,
577 OPTION_POLL_FREQUENCY, OPTION_WINDOW_MAX)),
578 (create_pickle_persistence(),
579 (OPTION_FEED_ID_SEQ, OPTION_FEEDS, OPTION_CATEGORIES)))
580 config = Config(cp)
581 config.initialize_proxy()
582 config.initialize_options()
583 convert_if_necessary(config)
584 return config
586 config_instance = None
587 def get_instance():
588 global config_instance
589 if config_instance is None:
590 config_instance = create_instance()
591 return config_instance