2007-11-02 17:37:14 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# gPodder - A media aggregator and podcast client
|
2018-01-28 19:39:53 +01:00
|
|
|
# Copyright (c) 2005-2018 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
|
2012-01-18 10:48:43 +01:00
|
|
|
from gpodder import jsonconfig
|
2007-11-02 17:37:14 +01:00
|
|
|
|
|
|
|
import atexit
|
2009-08-24 15:31:25 +02:00
|
|
|
import os
|
2012-01-03 23:59:19 +01:00
|
|
|
import shutil
|
2007-11-02 17:37:14 +01:00
|
|
|
import time
|
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
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
defaults = {
|
2010-12-20 01:21:41 +01:00
|
|
|
# External applications used for playback
|
2012-01-03 23:59:19 +01:00
|
|
|
'player': {
|
|
|
|
'audio': 'default',
|
|
|
|
'video': 'default',
|
|
|
|
},
|
2010-12-20 01:21:41 +01:00
|
|
|
|
|
|
|
# gpodder.net settings
|
2012-01-03 23:59:19 +01:00
|
|
|
'mygpo': {
|
|
|
|
'enabled': False,
|
|
|
|
'server': 'gpodder.net',
|
|
|
|
'username': '',
|
|
|
|
'password': '',
|
|
|
|
'device': {
|
|
|
|
'uid': util.get_hostname(),
|
|
|
|
'type': 'desktop',
|
|
|
|
'caption': _('gPodder on %s') % util.get_hostname(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
# Various limits (downloading, updating, etc..)
|
|
|
|
'limit': {
|
|
|
|
'bandwidth': {
|
|
|
|
'enabled': False,
|
|
|
|
'kbps': 500.0, # maximum kB/s per download
|
|
|
|
},
|
|
|
|
'downloads': {
|
|
|
|
'enabled': True,
|
|
|
|
'concurrent': 1,
|
2018-04-29 15:59:22 +02:00
|
|
|
'concurrent_max': 16,
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
|
|
|
'episodes': 200, # max episodes per feed
|
|
|
|
},
|
|
|
|
|
2014-05-17 11:46:09 +02:00
|
|
|
# Behavior of downloads
|
|
|
|
'downloads': {
|
|
|
|
'chronological_order': True, # download older episodes first
|
|
|
|
},
|
|
|
|
|
2012-02-05 18:20:59 +01:00
|
|
|
# Automatic feed updates, download removal and retry on download timeout
|
2012-01-03 23:59:19 +01:00
|
|
|
'auto': {
|
|
|
|
'update': {
|
|
|
|
'enabled': False,
|
|
|
|
'frequency': 20, # minutes
|
|
|
|
},
|
|
|
|
|
|
|
|
'cleanup': {
|
|
|
|
'days': 7,
|
|
|
|
'played': False,
|
|
|
|
'unplayed': False,
|
|
|
|
'unfinished': True,
|
|
|
|
},
|
2012-02-05 18:20:59 +01:00
|
|
|
|
|
|
|
'retries': 3, # number of retries when downloads time out
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
|
|
|
|
2014-10-29 15:15:37 +01:00
|
|
|
# Software updates from gpodder.org
|
2012-03-03 21:09:05 +01:00
|
|
|
'software_update': {
|
2014-10-29 15:15:37 +01:00
|
|
|
'check_on_startup': True, # check for updates on start
|
2012-03-03 21:09:05 +01:00
|
|
|
'last_check': 0, # unix timestamp of last update check
|
|
|
|
'interval': 5, # interval (in days) to check for updates
|
|
|
|
},
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
'ui': {
|
2012-02-20 18:03:00 +01:00
|
|
|
# Settings for the Command-Line Interface
|
|
|
|
'cli': {
|
|
|
|
'colors': True,
|
|
|
|
},
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
# Settings for the Gtk UI
|
|
|
|
'gtk': {
|
|
|
|
'state': {
|
|
|
|
'main_window': {
|
|
|
|
'width': 700,
|
|
|
|
'height': 500,
|
|
|
|
'x': -1, 'y': -1, 'maximized': False,
|
|
|
|
|
|
|
|
'paned_position': 200,
|
2013-03-05 11:36:41 +01:00
|
|
|
'episode_list_size': 200,
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
|
|
|
'episode_selector': {
|
|
|
|
'width': 600,
|
|
|
|
'height': 400,
|
|
|
|
'x': -1, 'y': -1, 'maximized': False,
|
|
|
|
},
|
|
|
|
'episode_window': {
|
|
|
|
'width': 500,
|
|
|
|
'height': 400,
|
|
|
|
'x': -1, 'y': -1, 'maximized': False,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2012-03-08 10:52:21 +01:00
|
|
|
'toolbar': False,
|
2012-01-03 23:59:19 +01:00
|
|
|
'new_episodes': 'show', # ignore, show, queue, download
|
2012-02-27 14:53:27 +01:00
|
|
|
'live_search_delay': 200,
|
2012-01-03 23:59:19 +01:00
|
|
|
|
|
|
|
'podcast_list': {
|
|
|
|
'all_episodes': True,
|
|
|
|
'sections': True,
|
|
|
|
'view_mode': 1,
|
|
|
|
'hide_empty': False,
|
|
|
|
},
|
|
|
|
|
|
|
|
'episode_list': {
|
|
|
|
'descriptions': True,
|
|
|
|
'view_mode': 1,
|
2014-01-28 02:32:44 +01:00
|
|
|
'columns': int('110', 2), # bitfield of visible columns
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
'download_list': {
|
|
|
|
'remove_finished': True,
|
|
|
|
},
|
2017-01-29 22:38:02 +01:00
|
|
|
|
2017-02-05 20:49:38 +01:00
|
|
|
'html_shownotes': True, # enable webkit renderer
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2012-07-09 21:08:40 +02:00
|
|
|
# Synchronization with portable devices (MP3 players, etc..)
|
2012-07-02 05:53:33 +02:00
|
|
|
'device_sync': {
|
2013-05-27 17:38:55 +02:00
|
|
|
'device_type': 'none', # Possible values: 'none', 'filesystem', 'ipod'
|
2018-02-01 07:59:22 +01:00
|
|
|
'device_folder': '/media',
|
2012-07-09 21:08:40 +02:00
|
|
|
|
2012-07-07 23:41:56 +02:00
|
|
|
'one_folder_per_podcast': True,
|
2012-07-02 05:53:33 +02:00
|
|
|
'skip_played_episodes': True,
|
|
|
|
'delete_played_episodes': False,
|
2012-07-09 21:08:40 +02:00
|
|
|
|
2012-07-02 05:53:33 +02:00
|
|
|
'max_filename_length': 999,
|
2012-07-09 21:08:40 +02:00
|
|
|
|
2012-08-30 03:41:46 +02:00
|
|
|
'custom_sync_name': '{episode.sortdate}_{episode.title}',
|
2012-07-02 05:53:33 +02:00
|
|
|
'custom_sync_name_enabled': False,
|
2012-07-09 21:08:40 +02:00
|
|
|
|
|
|
|
'after_sync': {
|
|
|
|
'mark_episodes_played': False,
|
|
|
|
'delete_episodes': False,
|
|
|
|
'sync_disks': False,
|
2012-07-02 05:53:33 +02:00
|
|
|
},
|
2013-02-01 04:36:21 +01:00
|
|
|
'playlists': {
|
|
|
|
'create': True,
|
|
|
|
'two_way_sync': False,
|
|
|
|
'use_absolute_path': True,
|
|
|
|
'folder': 'Playlists',
|
|
|
|
}
|
|
|
|
|
2012-07-09 21:08:40 +02:00
|
|
|
},
|
2012-07-02 05:53:33 +02:00
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
'youtube': {
|
2012-10-01 10:56:26 +02:00
|
|
|
'preferred_fmt_id': 18, # default fmt_id (see fallbacks in youtube.py)
|
|
|
|
'preferred_fmt_ids': [], # for advanced uses (custom fallback sequence)
|
2015-05-20 21:10:57 +02:00
|
|
|
'api_key_v3': '', # API key, register for one at https://developers.google.com/youtube/v3/
|
2012-01-03 23:59:19 +01:00
|
|
|
},
|
2012-02-04 21:43:37 +01:00
|
|
|
|
2014-09-30 13:35:23 +02:00
|
|
|
'vimeo': {
|
2015-11-13 22:25:37 +01:00
|
|
|
'fileformat': '720p', # preferred file format (see vimeo.py)
|
2014-09-30 13:35:23 +02:00
|
|
|
},
|
|
|
|
|
2012-02-04 21:43:37 +01:00
|
|
|
'extensions': {
|
|
|
|
'enabled': [],
|
|
|
|
},
|
2007-11-02 17:37:14 +01:00
|
|
|
}
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
# The sooner this goes away, the better
|
|
|
|
gPodderSettings_LegacySupport = {
|
|
|
|
'player': 'player.audio',
|
|
|
|
'videoplayer': 'player.video',
|
|
|
|
'limit_rate': 'limit.bandwidth.enabled',
|
|
|
|
'limit_rate_value': 'limit.bandwidth.kbps',
|
|
|
|
'max_downloads_enabled': 'limit.downloads.enabled',
|
|
|
|
'max_downloads': 'limit.downloads.concurrent',
|
|
|
|
'episode_old_age': 'auto.cleanup.days',
|
|
|
|
'auto_remove_played_episodes': 'auto.cleanup.played',
|
|
|
|
'auto_remove_unfinished_episodes': 'auto.cleanup.unfinished',
|
|
|
|
'auto_remove_unplayed_episodes': 'auto.cleanup.unplayed',
|
|
|
|
'max_episodes_per_feed': 'limit.episodes',
|
|
|
|
'show_toolbar': 'ui.gtk.toolbar',
|
|
|
|
'episode_list_descriptions': 'ui.gtk.episode_list.descriptions',
|
|
|
|
'podcast_list_view_all': 'ui.gtk.podcast_list.all_episodes',
|
|
|
|
'podcast_list_sections': 'ui.gtk.podcast_list.sections',
|
|
|
|
'episode_list_view_mode': 'ui.gtk.episode_list.view_mode',
|
|
|
|
'podcast_list_view_mode': 'ui.gtk.podcast_list.view_mode',
|
|
|
|
'podcast_list_hide_boring': 'ui.gtk.podcast_list.hide_empty',
|
|
|
|
'episode_list_columns': 'ui.gtk.episode_list.columns',
|
|
|
|
'auto_cleanup_downloads': 'ui.gtk.download_list.remove_finished',
|
|
|
|
'auto_update_feeds': 'auto.update.enabled',
|
|
|
|
'auto_update_frequency': 'auto.update.frequency',
|
|
|
|
'auto_download': 'ui.gtk.new_episodes',
|
|
|
|
}
|
2010-12-20 00:52:40 +01:00
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
2008-11-19 18:16:37 +01:00
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
|
2012-02-21 10:22:49 +01:00
|
|
|
def config_value_to_string(config_value):
|
|
|
|
config_type = type(config_value)
|
|
|
|
|
|
|
|
if config_type == list:
|
|
|
|
return ','.join(map(config_value_to_string, config_value))
|
2016-11-21 23:13:46 +01:00
|
|
|
elif config_type in (str, str):
|
2012-02-21 10:22:49 +01:00
|
|
|
return config_value
|
|
|
|
else:
|
|
|
|
return str(config_value)
|
|
|
|
|
2018-02-11 00:22:00 +01:00
|
|
|
|
2012-02-21 10:22:49 +01:00
|
|
|
def string_to_config_value(new_value, old_value):
|
|
|
|
config_type = type(old_value)
|
|
|
|
|
|
|
|
if config_type == list:
|
2016-11-21 23:13:46 +01:00
|
|
|
return [_f for _f in [x.strip() for x in new_value.split(',')] if _f]
|
2012-02-21 10:22:49 +01:00
|
|
|
elif config_type == bool:
|
|
|
|
return (new_value.strip().lower() in ('1', 'true'))
|
|
|
|
else:
|
2012-02-27 12:22:40 +01:00
|
|
|
return config_type(new_value)
|
2012-02-21 10:22:49 +01:00
|
|
|
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
class Config(object):
|
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
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
def __init__(self, filename='gpodder.json'):
|
2012-01-18 10:48:43 +01:00
|
|
|
self.__json_config = jsonconfig.JsonConfig(default=defaults,
|
2012-01-18 10:44:14 +01:00
|
|
|
on_key_changed=self._on_key_changed)
|
2007-11-02 17:37:14 +01:00
|
|
|
self.__save_thread = None
|
|
|
|
self.__filename = filename
|
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
|
|
|
|
2011-12-05 09:24:53 +01:00
|
|
|
# If there is no configuration file, we create one here (bug 1511)
|
|
|
|
if not os.path.exists(self.__filename):
|
|
|
|
self.save()
|
|
|
|
|
2012-01-03 23:59:19 +01:00
|
|
|
atexit.register(self.__atexit)
|
2007-11-02 17:37:14 +01:00
|
|
|
|
2012-01-09 13:42:16 +01:00
|
|
|
def register_defaults(self, defaults):
|
|
|
|
"""
|
|
|
|
Register default configuration options (e.g. for extensions)
|
|
|
|
|
|
|
|
This function takes a dictionary that will be merged into the
|
|
|
|
current configuration if the keys don't yet exist. This can
|
|
|
|
be used to add a default configuration for extension modules.
|
|
|
|
"""
|
|
|
|
self.__json_config._merge_keys(defaults)
|
|
|
|
|
2008-05-02 17:36:43 +02:00
|
|
|
def add_observer(self, callback):
|
|
|
|
"""
|
|
|
|
Add a callback function as observer. This callback
|
2012-01-03 23:59:19 +01:00
|
|
|
will be called when a setting changes. It should
|
2008-05-02 17:36:43 +02:00
|
|
|
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
|
|
|
|
2012-02-20 21:16:36 +01:00
|
|
|
def all_keys(self):
|
|
|
|
return self.__json_config._keys_iter()
|
|
|
|
|
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:
|
2012-07-10 13:52:34 +02:00
|
|
|
self.__save_thread = util.run_in_background(self.save_thread_proc, True)
|
2007-11-02 17:37:14 +01:00
|
|
|
|
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()
|
|
|
|
|
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
|
|
|
|
|
|
|
try:
|
2018-04-15 18:59:20 +02:00
|
|
|
fp = open(filename + '.tmp', 'wt')
|
2012-01-03 23:59:19 +01:00
|
|
|
fp.write(repr(self.__json_config))
|
|
|
|
fp.close()
|
2018-04-15 18:59:20 +02:00
|
|
|
util.atomic_rename(filename + '.tmp', filename)
|
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)
|
2018-04-15 18:59:20 +02:00
|
|
|
util.delete_file(filename + '.tmp')
|
2011-07-15 16:32:06 +02:00
|
|
|
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
|
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
if os.path.exists(self.__filename):
|
|
|
|
try:
|
2012-02-24 16:58:05 +01:00
|
|
|
data = open(self.__filename, 'rt').read()
|
2012-02-20 18:03:00 +01:00
|
|
|
new_keys_added = self.__json_config._restore(data)
|
2009-08-24 15:31:25 +02:00
|
|
|
except:
|
2011-07-15 16:32:06 +02:00
|
|
|
logger.warn('Cannot parse config file: %s',
|
|
|
|
self.__filename, exc_info=True)
|
2012-02-20 18:03:00 +01:00
|
|
|
new_keys_added = False
|
|
|
|
|
|
|
|
if new_keys_added:
|
|
|
|
logger.info('New default keys added - saving config.')
|
|
|
|
self.save()
|
2009-08-24 15:31:25 +02:00
|
|
|
|
2008-02-07 22:19:03 +01:00
|
|
|
def toggle_flag(self, name):
|
2012-01-03 23:59:19 +01:00
|
|
|
setattr(self, name, not getattr(self, name))
|
2008-02-07 22:19:03 +01:00
|
|
|
|
|
|
|
def update_field(self, name, new_value):
|
2012-02-21 10:19:41 +01:00
|
|
|
"""Update a config field, converting strings to the right types"""
|
2012-02-21 10:22:49 +01:00
|
|
|
old_value = self._lookup(name)
|
|
|
|
new_value = string_to_config_value(new_value, old_value)
|
2012-01-03 23:59:19 +01:00
|
|
|
setattr(self, name, new_value)
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _on_key_changed(self, name, old_value, value):
|
|
|
|
if 'ui.gtk.state' not in name:
|
|
|
|
# Only log non-UI state changes
|
|
|
|
logger.debug('%s: %s -> %s', name, old_value, value)
|
|
|
|
for observer in self.__observers:
|
2008-02-07 22:19:03 +01:00
|
|
|
try:
|
2012-01-03 23:59:19 +01:00
|
|
|
observer(name, old_value, value)
|
2016-11-21 23:13:46 +01:00
|
|
|
except Exception as exception:
|
2012-01-03 23:59:19 +01:00
|
|
|
logger.error('Error while calling observer %r: %s',
|
|
|
|
observer, exception, exc_info=True)
|
|
|
|
|
|
|
|
self.schedule_save()
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
if name in gPodderSettings_LegacySupport:
|
|
|
|
name = gPodderSettings_LegacySupport[name]
|
|
|
|
|
|
|
|
return getattr(self.__json_config, name)
|
2008-02-07 22:19:03 +01:00
|
|
|
|
2009-08-24 15:31:25 +02:00
|
|
|
def __setattr__(self, name, value):
|
2012-01-03 23:59:19 +01:00
|
|
|
if name.startswith('_'):
|
2009-08-24 15:31:25 +02:00
|
|
|
object.__setattr__(self, name, value)
|
2012-01-03 23:59:19 +01:00
|
|
|
return
|
|
|
|
|
|
|
|
if name in gPodderSettings_LegacySupport:
|
|
|
|
name = gPodderSettings_LegacySupport[name]
|
|
|
|
|
|
|
|
setattr(self.__json_config, name, value)
|