2007-11-02 17:37:14 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# gPodder - A media aggregator and podcast client
|
2011-04-01 18:59:42 +02:00
|
|
|
# Copyright (c) 2005-2011 Thomas Perl and the gPodder Team
|
2007-11-02 17:37:14 +01:00
|
|
|
#
|
|
|
|
# gPodder is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# gPodder is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# config.py -- gPodder Configuration Manager
|
|
|
|
# Thomas Perl <thp@perli.net> 2007-11-02
|
|
|
|
#
|
|
|
|
|
|
|
|
|
2008-04-06 02:19:03 +02:00
|
|
|
import gpodder
|
2008-01-15 14:54:22 +01:00
|
|
|
from gpodder import util
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
import atexit
|
2009-08-24 15:31:25 +02:00
|
|
|
import os
|
2007-11-02 17:37:14 +01:00
|
|
|
import time
|
|
|
|
import threading
|
|
|
|
import ConfigParser
|
2011-07-15 16:32:06 +02:00
|
|
|
import logging
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2009-05-07 16:26:07 +02:00
|
|
|
_ = gpodder.gettext
|
|
|
|
|
2007-11-02 17:37:14 +01:00
|
|
|
gPodderSettings = {
|
2010-12-20 01:21:41 +01:00
|
|
|
# External applications used for playback
|
|
|
|
'player': 'default',
|
|
|
|
'videoplayer': 'default',
|
|
|
|
|
|
|
|
# gpodder.net settings
|
|
|
|
'mygpo_enabled': False,
|
|
|
|
'mygpo_server': 'gpodder.net',
|
|
|
|
'mygpo_username': '',
|
|
|
|
'mygpo_password': '',
|
|
|
|
'mygpo_device_uid': util.get_hostname(),
|
|
|
|
'mygpo_device_type': 'desktop',
|
|
|
|
'mygpo_device_caption': _('gPodder on %s') % util.get_hostname(),
|
|
|
|
|
|
|
|
# Download options
|
|
|
|
'limit_rate': False,
|
|
|
|
'limit_rate_value': 500.0,
|
|
|
|
'max_downloads_enabled': True,
|
|
|
|
'max_downloads': 1,
|
|
|
|
|
2010-12-20 01:34:04 +01:00
|
|
|
# Automatic removal of downloads
|
2010-12-20 01:21:41 +01:00
|
|
|
'episode_old_age': 7,
|
2010-12-20 01:34:04 +01:00
|
|
|
'auto_remove_played_episodes': False,
|
2011-07-03 22:47:02 +02:00
|
|
|
'auto_remove_unfinished_episodes': True,
|
2010-12-20 01:34:04 +01:00
|
|
|
'auto_remove_unplayed_episodes': False,
|
|
|
|
|
|
|
|
# Periodic check for new episodes
|
|
|
|
'auto_update_feeds': False,
|
|
|
|
'auto_update_frequency': 20,
|
|
|
|
|
|
|
|
# Limits
|
2010-12-20 01:21:41 +01:00
|
|
|
'max_episodes_per_feed': 200,
|
|
|
|
|
2010-12-20 01:34:04 +01:00
|
|
|
# View settings
|
|
|
|
'show_toolbar': True,
|
|
|
|
'episode_list_descriptions': True,
|
|
|
|
'podcast_list_view_all': True,
|
2011-07-27 14:19:02 +02:00
|
|
|
'podcast_list_sections': True,
|
2010-12-20 01:34:04 +01:00
|
|
|
'enable_html_shownotes': True,
|
|
|
|
'enable_notifications': True,
|
|
|
|
|
|
|
|
# Display list filter configuration
|
|
|
|
'episode_list_view_mode': 1,
|
2011-04-11 13:09:50 +02:00
|
|
|
'episode_list_columns': int('101', 2), # bitfield of visible columns
|
2010-12-20 01:34:04 +01:00
|
|
|
'podcast_list_view_mode': 1,
|
|
|
|
'podcast_list_hide_boring': False,
|
|
|
|
|
2010-12-20 01:21:41 +01:00
|
|
|
# URLs to OPML files
|
2010-12-20 01:37:25 +01:00
|
|
|
'example_opml': 'http://gpodder.org/directory.opml',
|
|
|
|
'toplist_opml': 'http://gpodder.org/toplist.opml',
|
2010-12-20 01:21:41 +01:00
|
|
|
|
2010-12-20 01:34:04 +01:00
|
|
|
# YouTube
|
|
|
|
'youtube_preferred_fmt_id': 18,
|
|
|
|
|
|
|
|
# Misc
|
2011-04-11 11:12:11 +02:00
|
|
|
'_paned_position': 200,
|
2010-12-20 01:34:04 +01:00
|
|
|
'rotation_mode': 0,
|
|
|
|
'mimetype_prefs': '',
|
|
|
|
'auto_cleanup_downloads': True,
|
|
|
|
'do_not_show_new_episodes_dialog': False,
|
|
|
|
'auto_download': 'never',
|
2007-11-02 17:37:14 +01:00
|
|
|
}
|
|
|
|
|
2010-12-20 00:52:40 +01:00
|
|
|
|
2008-11-19 18:16:37 +01:00
|
|
|
# Helper function to add window-specific properties (position and size)
|
2009-09-02 12:34:57 +02:00
|
|
|
def window_props(config_prefix, x=-1, y=-1, width=700, height=500):
|
2008-11-19 18:16:37 +01:00
|
|
|
return {
|
2010-12-20 00:52:40 +01:00
|
|
|
config_prefix+'_x': x,
|
|
|
|
config_prefix+'_y': y,
|
|
|
|
config_prefix+'_width': width,
|
|
|
|
config_prefix+'_height': height,
|
|
|
|
config_prefix+'_maximized': False,
|
2008-11-19 18:16:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Register window-specific properties
|
2011-04-11 11:12:11 +02:00
|
|
|
gPodderSettings.update(window_props('_main_window', width=700, height=500))
|
|
|
|
gPodderSettings.update(window_props('_episode_selector', width=600, height=400))
|
|
|
|
gPodderSettings.update(window_props('_episode_window', width=500, height=400))
|
2008-11-19 18:16:37 +01:00
|
|
|
|
2011-07-15 16:32:06 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
2008-11-19 18:16:37 +01:00
|
|
|
|
2007-11-02 17:37:14 +01:00
|
|
|
class Config(dict):
|
|
|
|
Settings = gPodderSettings
|
2008-04-06 02:19:03 +02:00
|
|
|
|
|
|
|
# Number of seconds after which settings are auto-saved
|
|
|
|
WRITE_TO_DISK_TIMEOUT = 60
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def __init__(self, filename='gpodder.conf'):
|
|
|
|
dict.__init__(self)
|
2007-11-02 17:37:14 +01:00
|
|
|
self.__save_thread = None
|
|
|
|
self.__filename = filename
|
|
|
|
self.__section = 'gpodder-conf-1'
|
2008-05-02 17:36:43 +02:00
|
|
|
self.__observers = []
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
self.load()
|
2009-08-13 20:39:00 +02:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
atexit.register( self.__atexit)
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
2007-11-02 17:37:14 +01:00
|
|
|
if name in self.Settings:
|
|
|
|
return self[name]
|
|
|
|
else:
|
2009-01-05 13:37:32 +01:00
|
|
|
raise AttributeError('%s is not a setting' % name)
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2008-05-02 17:36:43 +02:00
|
|
|
def add_observer(self, callback):
|
|
|
|
"""
|
|
|
|
Add a callback function as observer. This callback
|
|
|
|
will be called when a setting changes. It should
|
|
|
|
have this signature:
|
|
|
|
|
|
|
|
observer(name, old_value, new_value)
|
|
|
|
|
|
|
|
The "name" is the setting name, the "old_value" is
|
|
|
|
the value that has been overwritten with "new_value".
|
|
|
|
"""
|
|
|
|
if callback not in self.__observers:
|
|
|
|
self.__observers.append(callback)
|
|
|
|
else:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Observer already added: %s', repr(callback))
|
2008-05-02 17:36:43 +02:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def remove_observer(self, callback):
|
|
|
|
"""
|
|
|
|
Remove an observer previously added to this object.
|
|
|
|
"""
|
|
|
|
if callback in self.__observers:
|
|
|
|
self.__observers.remove(callback)
|
2007-11-02 17:37:14 +01:00
|
|
|
else:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Observer not added: %s', repr(callback))
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def schedule_save(self):
|
2008-02-26 16:49:59 +01:00
|
|
|
if self.__save_thread is None:
|
2009-08-24 15:31:25 +02:00
|
|
|
self.__save_thread = threading.Thread(target=self.save_thread_proc)
|
|
|
|
self.__save_thread.setDaemon(True)
|
2007-11-02 17:37:14 +01:00
|
|
|
self.__save_thread.start()
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def save_thread_proc(self):
|
|
|
|
time.sleep(self.WRITE_TO_DISK_TIMEOUT)
|
2008-04-22 21:16:30 +02:00
|
|
|
if self.__save_thread is not None:
|
2007-11-02 17:37:14 +01:00
|
|
|
self.save()
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def __atexit(self):
|
2008-04-22 21:16:30 +02:00
|
|
|
if self.__save_thread is not None:
|
2007-11-02 17:37:14 +01:00
|
|
|
self.save()
|
|
|
|
|
2010-04-25 21:11:28 +02:00
|
|
|
def get_backup(self):
|
|
|
|
"""Create a backup of the current settings
|
|
|
|
|
|
|
|
Returns a dictionary with the current settings which can
|
|
|
|
be used with "restore_backup" (see below) to restore the
|
|
|
|
state of the configuration object at a future point in time.
|
|
|
|
"""
|
|
|
|
return dict(self)
|
|
|
|
|
|
|
|
def restore_backup(self, backup):
|
|
|
|
"""Restore a previously-created backup
|
|
|
|
|
|
|
|
Restore a previously-created configuration backup (created
|
|
|
|
with "get_backup" above) and notify any observer about the
|
|
|
|
changed settings.
|
|
|
|
"""
|
|
|
|
for key, value in backup.iteritems():
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def save(self, filename=None):
|
|
|
|
if filename is None:
|
|
|
|
filename = self.__filename
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.info('Flushing settings to disk')
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
parser = ConfigParser.RawConfigParser()
|
2009-08-24 15:31:25 +02:00
|
|
|
parser.add_section(self.__section)
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2010-12-20 00:52:40 +01:00
|
|
|
for key, default in self.Settings.items():
|
|
|
|
fieldtype = type(default)
|
2009-08-24 15:31:25 +02:00
|
|
|
parser.set(self.__section, key, getattr(self, key, default))
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
try:
|
2009-08-24 15:31:25 +02:00
|
|
|
parser.write(open(filename, 'w'))
|
2007-11-02 17:37:14 +01:00
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.error('Cannot write settings to %s', filename)
|
|
|
|
raise
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
self.__save_thread = None
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def load(self, filename=None):
|
2008-04-22 21:16:30 +02:00
|
|
|
if filename is not None:
|
2007-11-02 17:37:14 +01:00
|
|
|
self.__filename = filename
|
|
|
|
|
|
|
|
parser = ConfigParser.RawConfigParser()
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
if os.path.exists(self.__filename):
|
|
|
|
try:
|
|
|
|
parser.read(self.__filename)
|
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Cannot parse config file: %s',
|
|
|
|
self.__filename, exc_info=True)
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2010-12-20 00:52:40 +01:00
|
|
|
for key, default in self.Settings.items():
|
|
|
|
fieldtype = type(default)
|
|
|
|
value = default
|
2007-11-02 17:37:14 +01:00
|
|
|
try:
|
2009-09-14 20:44:43 +02:00
|
|
|
if not parser.has_section(self.__section):
|
|
|
|
value = default
|
|
|
|
elif fieldtype == int:
|
2009-08-24 15:31:25 +02:00
|
|
|
value = parser.getint(self.__section, key)
|
2007-11-02 17:37:14 +01:00
|
|
|
elif fieldtype == float:
|
2009-08-24 15:31:25 +02:00
|
|
|
value = parser.getfloat(self.__section, key)
|
2007-11-02 17:37:14 +01:00
|
|
|
elif fieldtype == bool:
|
2009-08-24 15:31:25 +02:00
|
|
|
value = parser.getboolean(self.__section, key)
|
2007-11-02 17:37:14 +01:00
|
|
|
else:
|
2009-08-24 15:31:25 +02:00
|
|
|
value = fieldtype(parser.get(self.__section, key))
|
2011-08-03 14:15:04 +02:00
|
|
|
except ConfigParser.NoOptionError:
|
|
|
|
# Not (yet) set in the file, use the default value
|
|
|
|
value = default
|
2007-11-02 17:37:14 +01:00
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Invalid value in %s for %s: %s',
|
|
|
|
self.__filename, key, value, exc_info=True)
|
2007-11-02 17:37:14 +01:00
|
|
|
value = default
|
|
|
|
|
|
|
|
self[key] = value
|
2008-02-07 22:19:03 +01:00
|
|
|
|
|
|
|
def toggle_flag(self, name):
|
|
|
|
if name in self.Settings:
|
2010-12-20 00:52:40 +01:00
|
|
|
default = self.Settings[name]
|
|
|
|
fieldtype = type(default)
|
2008-02-07 22:19:03 +01:00
|
|
|
if fieldtype == bool:
|
|
|
|
setattr(self, name, not getattr(self, name))
|
|
|
|
else:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Cannot toggle value: %s (not boolean)', name)
|
2008-02-07 22:19:03 +01:00
|
|
|
else:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Invalid setting name: %s', name)
|
2008-02-07 22:19:03 +01:00
|
|
|
|
|
|
|
def update_field(self, name, new_value):
|
|
|
|
if name in self.Settings:
|
2010-12-20 00:52:40 +01:00
|
|
|
default = self.Settings[name]
|
|
|
|
fieldtype = type(default)
|
2008-02-07 22:19:03 +01:00
|
|
|
try:
|
|
|
|
new_value = fieldtype(new_value)
|
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Cannot convert %s to %s.', str(new_value),
|
|
|
|
fieldtype.__name__, exc_info=True)
|
2008-02-07 22:19:03 +01:00
|
|
|
return False
|
|
|
|
setattr(self, name, new_value)
|
|
|
|
return True
|
|
|
|
else:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.info('Ignoring invalid setting: %s', name)
|
2008-02-07 22:19:03 +01:00
|
|
|
return False
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def __setattr__(self, name, value):
|
2007-11-02 17:37:14 +01:00
|
|
|
if name in self.Settings:
|
2010-12-20 00:52:40 +01:00
|
|
|
default = self.Settings[name]
|
|
|
|
fieldtype = type(default)
|
2007-11-02 17:37:14 +01:00
|
|
|
try:
|
|
|
|
if self[name] != fieldtype(value):
|
2008-05-02 17:36:43 +02:00
|
|
|
old_value = self[name]
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.info('Update %s: %s => %s', name, old_value, value)
|
2007-11-02 17:37:14 +01:00
|
|
|
self[name] = fieldtype(value)
|
2008-05-02 17:36:43 +02:00
|
|
|
for observer in self.__observers:
|
|
|
|
try:
|
|
|
|
# Notify observer about config change
|
|
|
|
observer(name, old_value, self[name])
|
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.error('Error while calling observer: %s',
|
|
|
|
repr(observer), exc_info=True)
|
2007-11-02 17:37:14 +01:00
|
|
|
self.schedule_save()
|
|
|
|
except:
|
2009-08-24 15:31:25 +02:00
|
|
|
raise ValueError('%s has to be of type %s' % (name, fieldtype.__name__))
|
2007-11-02 17:37:14 +01:00
|
|
|
else:
|
2009-08-24 15:31:25 +02:00
|
|
|
object.__setattr__(self, name, value)
|
2007-11-02 17:37:14 +01:00
|
|
|
|