New Configuration/Settings Manager; massive code clean-ups

git-svn-id: svn://svn.berlios.de/gpodder/trunk@447 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
Thomas Perl 2007-11-02 16:37:14 +00:00
parent b89489e961
commit ac149c7b7a
10 changed files with 402 additions and 408 deletions

View File

@ -1,3 +1,29 @@
Fri, 02 Nov 2007 17:28:05 +0100 <thp@perli.net>
New Configuration/Settings Manager; massive code clean-ups
* src/gpodder/config.py: Added new Configuration Manager that
automatically keeps track of saving changed values and is also able to
watch GTK widgets for changes; this should simplify our settings
management and give us a single place for maintaining settings
* src/gpodder/download.py: Access settings from new config manager
* src/gpodder/gui.py: Make use of new config manager to connect
widgets and settings from the GUI directly to the config manager;
remove manual loading and saving of settings; auto-connect as much as
possible in the gPodderProperties dialog to get real-time automatic
configuration saving; fix the other code to use new config manager
* src/gpodder/libgpodder.py: MASSIVE code clean-up; removed lots of
old cruft and dead code that has been lying around in libgpodder for
some time now; remove configuration code; utilize config manager;
unify DownloadHistory+PlaybackHistory in new HistoryStore class;
reduce number of import statements
* src/gpodder/libipodsync.py: Access settings from new config manager
* src/gpodder/libplayers.py: Removed dotdesktop_command()
* src/gpodder/libpodcasts.py: Move locking functionality into this
module, as locking is only used here; access config from new manager
* src/gpodder/services.py: Use config manager to get settings
* src/gpodder/util.py: Add format_desktop_command() function, based on
the dotdesktop_command() function from libplayers
Fri, 02 Nov 2007 07:49:38 +0100 <thp@perli.net>
Add ability to open download folder from channel's context menu

224
src/gpodder/config.py Normal file
View File

@ -0,0 +1,224 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (C) 2005-2007 Thomas Perl <thp at perli.net>
#
# 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
#
import gtk
import gobject
from gpodder.liblogger import log
import atexit
import os.path
import time
import threading
import ConfigParser
gPodderSettings = {
# General settings
'player': ( str, 'xdg-open' ),
'opml_url': ( str, 'http://gpodder.berlios.de/directory.opml' ),
'http_proxy': ( str, '' ),
'ftp_proxy': ( str, '' ),
'custom_sync_name': ( str, '{episode.basename}' ),
'custom_sync_name_enabled': ( bool, True ),
'max_downloads': ( int, 3 ),
'max_downloads_enabled': ( bool, False ),
'limit_rate': ( bool, False ),
'limit_rate_value': ( float, 500.0 ),
'bittorrent_dir': ( str, os.path.expanduser( '~/gpodder-downloads/torrents') ),
# Boolean config flags
'update_on_startup': ( bool, False ),
'download_after_update': ( bool, False ),
'use_gnome_bittorrent': ( bool, True ),
'only_sync_not_played': ( bool, False ),
'proxy_use_environment': ( bool, True ),
'update_tags': ( bool, False ),
# Settings that are updated directly in code
'ipod_mount': ( str, '/media/ipod' ),
'mp3_player_folder': ( str, '/media/usbdisk' ),
'device_type': ( str, 'none' ),
'download_dir': ( str, os.path.expanduser( '~/gpodder-downloads') ),
# Special settings (not in preferences)
'default_new': ( int, 1 ),
# Window and paned positions
'main_window_x': ( int, 100 ),
'main_window_y': ( int, 100 ),
'main_window_width': ( int, 700 ),
'main_window_height': ( int, 500 ),
'paned_position': ( int, 200 ),
}
class Config(dict):
Settings = gPodderSettings
def __init__( self, filename = 'gpodder.conf'):
dict.__init__( self)
self.__save_thread = None
self.__filename = filename
self.__section = 'gpodder-conf-1'
atexit.register( self.__atexit)
self.load()
def __getattr__( self, name):
if name in self.Settings:
( fieldtype, default ) = self.Settings[name]
return self[name]
else:
raise AttributeError
def connect_gtk_editable( self, name, editable):
if name in self.Settings:
editable.delete_text( 0, -1)
editable.insert_text( str(getattr( self, name)))
editable.connect( 'changed', lambda editable: setattr( self, name, editable.get_chars( 0, -1)))
else:
raise ValueError( '%s is not a setting' % name)
def connect_gtk_spinbutton( self, name, spinbutton):
if name in self.Settings:
spinbutton.set_value( getattr( self, name))
spinbutton.connect( 'value-changed', lambda spinbutton: setattr( self, name, spinbutton.get_value()))
else:
raise ValueError( '%s is not a setting' % name)
def connect_gtk_paned( self, name, paned):
if name in self.Settings:
paned.set_position( getattr( self, name))
paned_child = paned.get_child1()
paned_child.connect( 'size-allocate', lambda x, y: setattr( self, name, paned.get_position()))
else:
raise ValueError( '%s is not a setting' % name)
def connect_gtk_togglebutton( self, name, togglebutton):
if name in self.Settings:
togglebutton.set_active( getattr( self, name))
togglebutton.connect( 'toggled', lambda togglebutton: setattr( self, name, togglebutton.get_active()))
else:
raise ValueError( '%s is not a setting' % name)
def connect_gtk_filechooser( self, name, filechooser):
if name in self.Settings:
filechooser.set_filename( getattr( self, name))
filechooser.connect( 'selection-changed', lambda filechooser: setattr( self, name, filechooser.get_filename()))
else:
raise ValueError( '%s is not a setting' % name)
def receive_configure_event( self, widget, event, config_prefix):
( x, y, width, height ) = map( lambda x: config_prefix + '_' + x, [ 'x', 'y', 'width', 'height' ])
( x_pos, y_pos ) = widget.get_position()
( width_size, height_size ) = widget.get_size()
setattr( self, x, x_pos)
setattr( self, y, y_pos)
setattr( self, width, width_size)
setattr( self, height, height_size)
def connect_gtk_window( self, window, config_prefix = 'main_window'):
( x, y, width, height ) = map( lambda x: config_prefix + '_' + x, [ 'x', 'y', 'width', 'height' ])
if set( ( x, y, width, height )).issubset( set( self.Settings)):
window.resize( getattr( self, width), getattr( self, height))
window.move( getattr( self, x), getattr( self, y))
window.connect( 'configure-event', self.receive_configure_event, config_prefix)
else:
raise ValueError( 'Missing settings in set: %s' % ', '.join( ( x, y, width, height )))
def schedule_save( self):
if self.__save_thread == None:
self.__save_thread = threading.Thread( target = self.save_thread_proc)
self.__save_thread.start()
def save_thread_proc( self):
for i in range( 100):
if self.__save_thread != None:
time.sleep( .1)
if self.__save_thread != None:
self.save()
def __atexit( self):
if self.__save_thread != None:
self.save()
def save( self, filename = None):
if filename != None:
self.__filename = filename
log( 'Flushing settings to disk', sender = self)
parser = ConfigParser.RawConfigParser()
parser.add_section( self.__section)
for ( key, ( fieldtype, default ) ) in self.Settings.items():
parser.set( self.__section, key, getattr( self, key, default))
try:
parser.write( open( self.__filename, 'w'))
except:
raise IOError( 'Cannot write to file: %s' % self.__filename)
self.__save_thread = None
def load( self, filename = None):
if filename != None:
self.__filename = filename
parser = ConfigParser.RawConfigParser()
try:
parser.read( self.__filename)
except:
pass
for ( key, ( fieldtype, default ) ) in self.Settings.items():
try:
if fieldtype == int:
value = parser.getint( self.__section, key)
elif fieldtype == float:
value = parser.getfloat( self.__section, key)
elif fieldtype == bool:
value = parser.getboolean( self.__section, key)
else:
value = fieldtype(parser.get( self.__section, key))
except:
value = default
self[key] = value
def __setattr__( self, name, value):
if name in self.Settings:
( fieldtype, default ) = self.Settings[name]
try:
if self[name] != fieldtype(value):
log( 'Update: %s = %s', name, value, sender = self)
self[name] = fieldtype(value)
self.schedule_save()
except:
raise ValueError( '%s has to be of type %s' % ( name, fieldtype.__name__ ))
else:
object.__setattr__( self, name, value)

View File

@ -45,14 +45,14 @@ class DownloadURLOpener(urllib.FancyURLopener):
def __init__( self, channel):
gl = libgpodder.gPodderLib()
if gl.proxy_use_environment:
if gl.config.proxy_use_environment:
proxies = None
else:
proxies = {}
if gl.http_proxy:
proxies['http'] = gl.http_proxy
if gl.ftp_proxy:
proxies['ftp'] = gl.ftp_proxy
if gl.config.http_proxy:
proxies['http'] = gl.config.http_proxy
if gl.config.ftp_proxy:
proxies['ftp'] = gl.config.ftp_proxy
self.channel = channel
urllib.FancyURLopener.__init__( self, proxies)
@ -81,8 +81,8 @@ class DownloadThread(threading.Thread):
self.tempname = os.path.join( os.path.dirname( self.filename), '.tmp-' + os.path.basename( self.filename))
gl = libgpodder.gPodderLib()
self.limit_rate = gl.limit_rate
self.limit_rate_value = gl.limit_rate_value
self.limit_rate = gl.config.limit_rate
self.limit_rate_value = gl.config.limit_rate_value
self.cancelled = False
self.start_time = 0.0

View File

@ -151,9 +151,10 @@ class gPodder(GladeWidget):
self.uar = None
gl = gPodderLib()
self.gPodder.resize( gl.main_window_width, gl.main_window_height)
self.gPodder.move( gl.main_window_x, gl.main_window_y)
self.channelPaned.set_position( gl.paned_position)
gl.config.connect_gtk_window( self.gPodder)
gl.config.connect_gtk_paned( 'paned_position', self.channelPaned)
while gtk.events_pending():
gtk.main_iteration( False)
@ -257,7 +258,7 @@ class gPodder(GladeWidget):
gl.clean_up_downloads( delete_partial = True)
# Now, update the feed cache, when everything's in place
self.update_feed_cache( force_update = gl.update_on_startup)
self.update_feed_cache( force_update = gl.config.update_on_startup)
def treeview_channels_button_pressed( self, treeview, event):
if event.button == 3:
@ -498,11 +499,11 @@ class gPodder(GladeWidget):
can_download = True
if util.file_type_by_extension( util.file_extension_from_url( url)) == 'torrent':
can_download = can_download or gPodderLib().use_gnome_bittorrent
can_download = can_download or gPodderLib().config.use_gnome_bittorrent
can_download = can_download and not can_cancel
can_play = can_play and not can_cancel and not can_download
can_transfer = can_play and gPodderLib().device_type != 'none'
can_transfer = can_play and gPodderLib().config.device_type != 'none'
self.toolPlay.set_sensitive( can_play)
self.toolDownload.set_sensitive( can_download)
@ -618,7 +619,7 @@ class gPodder(GladeWidget):
for channel in downloaded_channels:
sync.set_progress_overall( i, len(downloaded_channels))
channel.load_settings()
sync.sync_channel( channel, sync_played_episodes = not gPodderLib().only_sync_not_played)
sync.sync_channel( channel, sync_played_episodes = not gPodderLib().config.only_sync_not_played)
i += 1
sync.set_progress_overall( i, len(downloaded_channels))
else:
@ -716,7 +717,7 @@ class gPodder(GladeWidget):
self.updateComboBox()
# download all new?
if force_update and gPodderLib().download_after_update:
if force_update and gPodderLib().config.download_after_update:
self.on_itemDownloadAllNew_activate( self.gPodder)
def download_podcast_by_url( self, url, want_message_dialog = True, widget = None):
@ -767,15 +768,6 @@ class gPodder(GladeWidget):
gl = gPodderLib()
size = self.gPodder.get_size()
pos = self.gPodder.get_position()
gl.main_window_width = size[0]
gl.main_window_height = size[1]
gl.main_window_x = pos[0]
gl.main_window_y = pos[1]
gl.paned_position = self.channelPaned.get_position()
gl.propertiesChanged()
self.gtk_main_quit()
sys.exit( 0)
@ -854,24 +846,24 @@ class gPodder(GladeWidget):
def on_sync_to_ipod_activate(self, widget, *args):
gl = gPodderLib()
if gl.device_type == 'none':
if gl.config.device_type == 'none':
title = _('No device configured')
message = _('To use the synchronization feature, please configure your device in the preferences dialog first.')
self.show_message( message, title)
return
if gl.device_type == 'ipod' and not ipod_supported():
if gl.config.device_type == 'ipod' and not ipod_supported():
title = _('Libraries needed: gpod, pymad')
message = _('To use the iPod synchronization feature, you need to install the <b>python-gpod</b> and <b>python-pymad</b> libraries from your distribution vendor. More information about the needed libraries can be found on the gPodder website.')
self.show_message( message, title)
return
if gl.device_type in [ 'ipod', 'filesystem' ]:
if gl.config.device_type in [ 'ipod', 'filesystem' ]:
sync_class = None
if gl.device_type == 'filesystem':
if gl.config.device_type == 'filesystem':
sync_class = gPodder_FSSync
elif gl.device_type == 'ipod':
elif gl.config.device_type == 'ipod':
sync_class = gPodder_iPodSync
if not sync_class:
@ -888,27 +880,27 @@ class gPodder(GladeWidget):
def on_cleanup_ipod_activate(self, widget, *args):
gl = gPodderLib()
if gl.device_type == 'none':
if gl.config.device_type == 'none':
title = _('No device configured')
message = _('To use the synchronization feature, please configure your device in the preferences dialog first.')
self.show_message( message, title)
return
if gl.device_type == 'ipod' and not ipod_supported():
if gl.config.device_type == 'ipod' and not ipod_supported():
title = _('Libraries needed: gpod, pymad')
message = _('To use the iPod synchronization feature, you need to install the <b>python-gpod</b> and <b>python-pymad</b> libraries from your distribution vendor. More information about the needed libraries can be found on the gPodder website.')
self.show_message( message, title)
return
if gl.device_type in [ 'ipod', 'filesystem' ]:
if gl.config.device_type in [ 'ipod', 'filesystem' ]:
sync_class = None
if gl.device_type == 'filesystem':
if gl.config.device_type == 'filesystem':
title = _('Delete podcasts from MP3 player?')
message = _('Do you really want to completely remove all episodes from your MP3 player?')
if self.show_confirmation( message, title):
sync_class = gPodder_FSSync
elif gl.device_type == 'ipod':
elif gl.config.device_type == 'ipod':
title = _('Delete podcasts on iPod?')
message = _('Do you really want to completely remove all episodes in the <b>Podcasts</b> playlist on your iPod?')
if self.show_confirmation( message, title):
@ -981,7 +973,7 @@ class gPodder(GladeWidget):
dlg.destroy()
def on_itemImportChannels_activate(self, widget, *args):
gPodderOpmlLister().get_channels_from_url( gPodderLib().opml_url, lambda url: self.add_new_channel(url,False), lambda: self.on_itemDownloadAllNew_activate( self.gPodder))
gPodderOpmlLister().get_channels_from_url( gPodderLib().config.opml_url, lambda url: self.add_new_channel(url,False), lambda: self.on_itemDownloadAllNew_activate( self.gPodder))
def on_btnTransfer_clicked(self, widget, *args):
self.on_treeAvailable_row_activated( widget, args)
@ -1311,43 +1303,45 @@ class gPodderProperties(GladeWidget):
def new(self):
self.callback_finished = None
gl = gPodderLib()
self.httpProxy.set_text( gl.http_proxy)
self.ftpProxy.set_text( gl.ftp_proxy)
self.openApp.set_text( gl.open_app)
self.iPodMountpoint.set_label( gl.ipod_mount)
self.ipodIcon.set_from_icon_name( 'gnome-dev-ipod', gtk.ICON_SIZE_BUTTON)
self.filesystemMountpoint.set_label( gl.mp3_player_folder)
self.opmlURL.set_text( gl.opml_url)
if gl.downloaddir:
self.chooserDownloadTo.set_filename( gl.downloaddir)
if gl.torrentdir:
self.chooserBitTorrentTo.set_filename( gl.torrentdir)
self.radio_copy_torrents.set_active( not gl.use_gnome_bittorrent)
self.radio_gnome_bittorrent.set_active( gl.use_gnome_bittorrent)
self.updateonstartup.set_active(gl.update_on_startup)
self.downloadnew.set_active(gl.download_after_update)
self.cbLimitDownloads.set_active(gl.limit_rate)
self.spinLimitDownloads.set_value(gl.limit_rate_value)
self.cbMaxDownloads.set_active(gl.max_downloads_enabled)
self.cbCustomSyncName.set_active(gl.custom_sync_name_enabled)
self.entryCustomSyncName.set_text(gl.custom_sync_name)
gl.config.connect_gtk_editable( 'http_proxy', self.httpProxy)
gl.config.connect_gtk_editable( 'ftp_proxy', self.ftpProxy)
gl.config.connect_gtk_editable( 'player', self.openApp)
gl.config.connect_gtk_editable( 'opml_url', self.opmlURL)
gl.config.connect_gtk_editable( 'custom_sync_name', self.entryCustomSyncName)
gl.config.connect_gtk_togglebutton( 'custom_sync_name_enabled', self.cbCustomSyncName)
gl.config.connect_gtk_togglebutton( 'download_after_update', self.downloadnew)
gl.config.connect_gtk_togglebutton( 'use_gnome_bittorrent', self.radio_gnome_bittorrent)
gl.config.connect_gtk_togglebutton( 'update_on_startup', self.updateonstartup)
gl.config.connect_gtk_togglebutton( 'only_sync_not_played', self.only_sync_not_played)
gl.config.connect_gtk_spinbutton( 'max_downloads', self.spinMaxDownloads)
gl.config.connect_gtk_togglebutton( 'max_downloads_enabled', self.cbMaxDownloads)
gl.config.connect_gtk_spinbutton( 'limit_rate_value', self.spinLimitDownloads)
gl.config.connect_gtk_togglebutton( 'limit_rate', self.cbLimitDownloads)
gl.config.connect_gtk_togglebutton( 'proxy_use_environment', self.cbEnvironmentVariables)
gl.config.connect_gtk_filechooser( 'bittorrent_dir', self.chooserBitTorrentTo)
self.entryCustomSyncName.set_sensitive( self.cbCustomSyncName.get_active())
self.spinMaxDownloads.set_value(gl.max_downloads)
self.only_sync_not_played.set_active(gl.only_sync_not_played)
self.radio_copy_torrents.set_active( not self.radio_gnome_bittorrent.get_active())
self.iPodMountpoint.set_label( gl.config.ipod_mount)
self.filesystemMountpoint.set_label( gl.config.mp3_player_folder)
self.chooserDownloadTo.set_filename( gl.downloaddir)
if tagging_supported():
self.updatetags.set_active(gl.update_tags)
gl.config.connect_gtk_togglebutton( 'update_tags', self.updatetags)
else:
self.updatetags.set_sensitive( False)
new_label = '%s (%s)' % ( self.updatetags.get_label(), _('needs python-eyed3') )
self.updatetags.set_label( new_label)
# device type
self.comboboxDeviceType.set_active( 0)
if gl.device_type == 'ipod':
if gl.config.device_type == 'ipod':
self.comboboxDeviceType.set_active( 1)
elif gl.device_type == 'filesystem':
elif gl.config.device_type == 'filesystem':
self.comboboxDeviceType.set_active( 2)
# the use proxy env vars check box
self.cbEnvironmentVariables.set_active( gl.proxy_use_environment)
# setup cell renderers
cellrenderer = gtk.CellRendererPixbuf()
self.comboPlayerApp.pack_start( cellrenderer, False)
@ -1356,6 +1350,8 @@ class gPodderProperties(GladeWidget):
self.comboPlayerApp.pack_start( cellrenderer, True)
self.comboPlayerApp.add_attribute( cellrenderer, 'markup', 0)
self.ipodIcon.set_from_icon_name( 'gnome-dev-ipod', gtk.ICON_SIZE_BUTTON)
def update_mountpoint( self, ipod):
if ipod == None or ipod.mount_point == None:
self.iPodMountpoint.set_label( '')
@ -1485,13 +1481,8 @@ class gPodderProperties(GladeWidget):
def on_btnOK_clicked(self, widget, *args):
gl = gPodderLib()
gl.http_proxy = self.httpProxy.get_text()
gl.ftp_proxy = self.ftpProxy.get_text()
gl.open_app = self.openApp.get_text()
gl.proxy_use_environment = self.cbEnvironmentVariables.get_active()
gl.ipod_mount = self.iPodMountpoint.get_label()
gl.mp3_player_folder = self.filesystemMountpoint.get_label()
gl.opml_url = self.opmlURL.get_text()
gl.config.ipod_mount = self.iPodMountpoint.get_label()
gl.config.mp3_player_folder = self.filesystemMountpoint.get_label()
if gl.downloaddir != self.chooserDownloadTo.get_filename():
new_download_dir = self.chooserDownloadTo.get_filename()
@ -1542,26 +1533,13 @@ class gPodderProperties(GladeWidget):
dlg.destroy()
gl.torrentdir = self.chooserBitTorrentTo.get_filename()
gl.use_gnome_bittorrent = self.radio_gnome_bittorrent.get_active()
gl.update_on_startup = self.updateonstartup.get_active()
gl.download_after_update = self.downloadnew.get_active()
gl.limit_rate = self.cbLimitDownloads.get_active()
gl.limit_rate_value = self.spinLimitDownloads.get_value()
gl.max_downloads_enabled = self.cbMaxDownloads.get_active()
gl.max_downloads = int(self.spinMaxDownloads.get_value())
gl.custom_sync_name = self.entryCustomSyncName.get_text()
gl.custom_sync_name_enabled = self.cbCustomSyncName.get_active()
gl.update_tags = self.updatetags.get_active()
gl.only_sync_not_played = self.only_sync_not_played.get_active()
device_type = self.comboboxDeviceType.get_active()
if device_type == 0:
gl.device_type = 'none'
gl.config.device_type = 'none'
elif device_type == 1:
gl.device_type = 'ipod'
gl.config.device_type = 'ipod'
elif device_type == 2:
gl.device_type = 'filesystem'
gl.propertiesChanged()
gl.config.device_type = 'filesystem'
self.gPodderProperties.destroy()
if self.callback_finished:
self.callback_finished()

View File

@ -24,6 +24,7 @@
#
import gtk
import gtk.gdk
import gobject
import thread
import threading
@ -32,58 +33,21 @@ import shutil
from gpodder import util
from gpodder import opml
from gpodder import config
from os.path import expanduser
from os.path import exists
from os.path import splitext
from liblogger import log
from os.path import dirname
from os.path import basename
from os.path import isfile
from os.path import isdir
from os.path import islink
from os.path import getsize
from os.path import join
import os
import os.path
from os import mkdir
from os import rmdir
from os import makedirs
from os import environ
from os import system
from os import unlink
from os import listdir
from glob import glob
import glob
import types
import subprocess
from libplayers import dotdesktop_command
from liblogger import log
from types import ListType
from gtk.gdk import PixbufLoader
from ConfigParser import ConfigParser
from xml.sax import saxutils
from urlparse import urlparse
from subprocess import Popen
import shlex
# global recursive lock for thread exclusion
globalLock = threading.RLock()
# my gpodderlib variable
g_podder_lib = None
# default url to use for opml directory on the web
default_opml_directory = 'http://gpodder.berlios.de/directory.opml'
def getLock():
globalLock.acquire()
def releaseLock():
globalLock.release()
# some awkward kind of "singleton" ;)
def gPodderLib():
global g_podder_lib
@ -92,154 +56,66 @@ def gPodderLib():
return g_podder_lib
class gPodderLibClass( object):
gpodderconf_section = 'gpodder-conf-1'
def __init__( self):
self.gpodderdir = expanduser( "~/.config/gpodder/")
util.make_directory( self.gpodderdir)
self.feed_cache_file = os.path.join( self.gpodderdir, 'feedcache.db')
self.channel_settings_file = os.path.join( self.gpodderdir, 'channelsettings.db')
self.channel_opml_file = os.path.join( self.gpodderdir, 'channels.opml')
self.__download_dir = None
try:
self.http_proxy = environ['http_proxy']
except:
self.http_proxy = ''
try:
self.ftp_proxy = environ['ftp_proxy']
except:
self.ftp_proxy = ''
self.proxy_use_environment = True
self.open_app = ""
self.ipod_mount = ""
self.opml_url = ""
self.update_on_startup = False
self.download_after_update = True
self.torrentdir = expanduser('~/gpodder-downloads/torrents')
self.use_gnome_bittorrent = True
self.limit_rate = False
self.limit_rate_value = 4.0
self.update_tags = False
self.desktop_link = _("gPodder downloads")
self.device_type = None
self.main_window_width = 600
self.main_window_height = 450
self.main_window_x = 0
self.main_window_y = 0
self.paned_position = 150
self.max_downloads = 3
self.max_downloads_enabled = False
self.custom_sync_name_enabled = False
self.custom_sync_name = '{episode.title}'
self.default_new = 1
self.mp3_player_folder = ""
self.only_sync_not_played = False
self.__download_history = DownloadHistory( self.get_download_history_filename())
self.__playback_history = PlaybackHistory( self.get_playback_history_filename())
self.loadConfig()
gpodder_dir = os.path.expanduser( '~/.config/gpodder/')
util.make_directory( gpodder_dir)
self.feed_cache_file = os.path.join( gpodder_dir, 'feedcache.db')
self.channel_settings_file = os.path.join( gpodder_dir, 'channelsettings.db')
self.channel_opml_file = os.path.join( gpodder_dir, 'channels.opml')
self.config = config.Config( os.path.join( gpodder_dir, 'gpodder.conf'))
self.__download_history = HistoryStore( os.path.join( gpodder_dir, 'download-history.txt'))
self.__playback_history = HistoryStore( os.path.join( gpodder_dir, 'playback-history.txt'))
def getConfigFilename( self):
return self.gpodderdir + "gpodder.conf"
def getChannelsFilename( self):
return self.gpodderdir + "channels.xml"
def get_download_history_filename( self):
return self.gpodderdir + 'download-history.txt'
def get_playback_history_filename( self):
return self.gpodderdir + 'playback-history.txt'
def get_device_name( self):
if self.device_type == 'ipod':
if self.config.device_type == 'ipod':
return _('iPod')
elif self.device_type == 'filesystem':
elif self.config.device_type == 'filesystem':
return _('MP3 player')
else:
log( 'Warning: Called get_device_name() when no device was selected.', sender = self)
return '(unknown device)'
def propertiesChanged( self):
# set new environment variables for subprocesses to use,
# but only if we are not told to passthru the env vars
if not self.proxy_use_environment:
environ['http_proxy'] = self.http_proxy
environ['ftp_proxy'] = self.ftp_proxy
# save settings for next startup
self.saveConfig()
def clean_up_downloads( self, delete_partial = False):
# Clean up temporary files left behind by old gPodder versions
if delete_partial:
temporary_files = glob( '%s/*/.tmp-*' % ( self.downloaddir, ))
temporary_files = glob.glob( '%s/*/.tmp-*' % ( self.downloaddir, ))
for tempfile in temporary_files:
util.delete_file( tempfile)
# Clean up empty download folders
download_dirs = glob( '%s/*' % ( self.downloaddir, ))
download_dirs = glob.glob( '%s/*' % ( self.downloaddir, ))
for ddir in download_dirs:
if isdir( ddir):
globr = glob( '%s/*' % ( ddir, ))
if not globr and ddir != self.torrentdir:
log( 'Stale download directory found: %s', basename( ddir))
if os.path.isdir( ddir):
globr = glob.glob( '%s/*' % ( ddir, ))
if not globr and ddir != self.config.bittorrent_dir:
log( 'Stale download directory found: %s', os.path.basename( ddir))
try:
rmdir( ddir)
os.rmdir( ddir)
log( 'Successfully removed %s.', ddir)
except:
log( 'Could not remove %s.', ddir)
def saveConfig( self):
parser = ConfigParser()
self.write_to_parser( parser, 'http_proxy', self.http_proxy)
self.write_to_parser( parser, 'ftp_proxy', self.ftp_proxy)
self.write_to_parser( parser, 'player', self.open_app)
self.write_to_parser( parser, 'proxy_use_env', self.proxy_use_environment)
self.write_to_parser( parser, 'ipod_mount', self.ipod_mount)
self.write_to_parser( parser, 'update_on_startup', self.update_on_startup)
self.write_to_parser( parser, 'download_after_update', self.download_after_update)
self.write_to_parser( parser, 'limit_rate', self.limit_rate)
self.write_to_parser( parser, 'limit_rate_value', self.limit_rate_value)
self.write_to_parser( parser, 'update_tags', self.update_tags)
self.write_to_parser( parser, 'opml_url', self.opml_url)
self.write_to_parser( parser, 'download_dir', self.downloaddir)
self.write_to_parser( parser, 'bittorrent_dir', self.torrentdir)
self.write_to_parser( parser, 'use_gnome_bittorrent', self.use_gnome_bittorrent)
self.write_to_parser( parser, 'device_type', self.device_type)
self.write_to_parser( parser, 'main_window_width', self.main_window_width)
self.write_to_parser( parser, 'max_downloads', self.max_downloads)
self.write_to_parser( parser, 'max_downloads_enabled', self.max_downloads_enabled)
self.write_to_parser( parser, 'custom_sync_name', self.custom_sync_name)
self.write_to_parser( parser, 'custom_sync_name_enabled', self.custom_sync_name_enabled)
self.write_to_parser( parser, 'default_new', self.default_new)
self.write_to_parser( parser, 'main_window_height', self.main_window_height)
self.write_to_parser( parser, 'main_window_x', self.main_window_x)
self.write_to_parser( parser, 'main_window_y', self.main_window_y)
self.write_to_parser( parser, 'paned_position', self.paned_position)
self.write_to_parser( parser, 'mp3_player_folder', self.mp3_player_folder)
self.write_to_parser( parser, 'only_sync_not_played', self.only_sync_not_played)
fn = self.getConfigFilename()
fp = open( fn, "w")
parser.write( fp)
fp.close()
def get_download_dir( self):
util.make_directory( self.__download_dir)
return self.__download_dir
util.make_directory( self.config.download_dir)
return self.config.download_dir
def set_download_dir( self, new_downloaddir):
if self.__download_dir and self.__download_dir != new_downloaddir:
log( 'Moving downloads from %s to %s', self.__download_dir, new_downloaddir)
if self.config.download_dir != new_downloaddir:
log( 'Moving downloads from %s to %s', self.config.download_dir, new_downloaddir)
try:
# Fix error when moving over disk boundaries
if isdir( new_downloaddir) and not listdir( new_downloaddir):
rmdir( new_downloaddir)
if os.path.isdir( new_downloaddir) and not os.listdir( new_downloaddir):
os.rmdir( new_downloaddir)
shutil.move( self.__download_dir, new_downloaddir)
shutil.move( self.config.download_dir, new_downloaddir)
except:
log( 'Error while moving %s to %s.', self.__download_dir, new_downloaddir)
log( 'Error while moving %s to %s.', self.config.download_dir, new_downloaddir)
return
self.__download_dir = new_downloaddir
self.config.download_dir = new_downloaddir
downloaddir = property(fget=get_download_dir,fset=set_download_dir)
@ -261,133 +137,21 @@ class gPodderLibClass( object):
def history_is_played( self, url):
return (url in self.__playback_history)
def get_from_parser( self, parser, option, default = ''):
try:
result = parser.get( self.gpodderconf_section, option)
return result
except:
return default
def get_int_from_parser( self, parser, option, default = 0):
try:
result = int(parser.get( self.gpodderconf_section, option))
return result
except:
return default
def get_float_from_parser( self, parser, option, default = 1.0):
try:
result = float(parser.get( self.gpodderconf_section, option))
return result
except:
return default
def get_boolean_from_parser( self, parser, option, default = False):
try:
result = parser.getboolean( self.gpodderconf_section, option)
return result
except:
return default
def write_to_parser( self, parser, option, value = ''):
if not parser.has_section( self.gpodderconf_section):
parser.add_section( self.gpodderconf_section)
try:
parser.set( self.gpodderconf_section, option, str(value))
except:
log( 'write_to_parser: could not write config (option=%s, value=%s)', option, value)
def loadConfig( self):
was_oldstyle = False
try:
fn = self.getConfigFilename()
if open(fn,'r').read(1) != '[':
log( 'seems like old-style config. trying to read it anyways..')
fp = open( fn, 'r')
http = fp.readline()
ftp = fp.readline()
app = fp.readline()
fp.close()
was_oldstyle = True
else:
parser = ConfigParser()
parser.read( fn)
if parser.has_section( self.gpodderconf_section):
http = self.get_from_parser( parser, 'http_proxy')
ftp = self.get_from_parser( parser, 'ftp_proxy')
app = self.get_from_parser( parser, 'player', 'gnome-open')
opml_url = self.get_from_parser( parser, 'opml_url', default_opml_directory)
if opml_url == 'http://share.opml.org/opml/topPodcasts.opml':
opml_url = 'http://gpodder.berlios.de/directory.opml'
self.proxy_use_environment = self.get_boolean_from_parser( parser, 'proxy_use_env', True)
self.ipod_mount = self.get_from_parser( parser, 'ipod_mount', '/media/ipod')
self.update_on_startup = self.get_boolean_from_parser(parser, 'update_on_startup', default=False)
self.download_after_update = self.get_boolean_from_parser(parser, 'download_after_update', default=False)
self.limit_rate = self.get_boolean_from_parser(parser, 'limit_rate', default=False)
self.limit_rate_value = self.get_float_from_parser(parser, 'limit_rate_value', default=4.0)
self.update_tags = self.get_boolean_from_parser(parser, 'update_tags', default=False)
self.downloaddir = self.get_from_parser( parser, 'download_dir', expanduser('~/gpodder-downloads'))
self.torrentdir = self.get_from_parser( parser, 'bittorrent_dir', expanduser('~/gpodder-downloads/torrents'))
self.use_gnome_bittorrent = self.get_boolean_from_parser( parser, 'use_gnome_bittorrent', default=True)
self.device_type = self.get_from_parser( parser, 'device_type', 'none')
self.main_window_width = self.get_int_from_parser( parser, 'main_window_width', 600)
self.main_window_height = self.get_int_from_parser( parser, 'main_window_height', 450)
self.main_window_x = self.get_int_from_parser( parser, 'main_window_x', 0)
self.main_window_y = self.get_int_from_parser( parser, 'main_window_y', 0)
self.paned_position = self.get_int_from_parser( parser, 'paned_position', 0)
self.max_downloads = self.get_int_from_parser( parser, 'max_downloads', 3)
self.max_downloads_enabled = self.get_boolean_from_parser(parser, 'max_downloads_enabled', default=False)
self.custom_sync_name = self.get_from_parser( parser, 'custom_sync_name', '')
self.custom_sync_name_enabled = self.get_boolean_from_parser(parser, 'custom_sync_name_enabled', default=False)
self.default_new = self.get_int_from_parser( parser, 'default_new', 1)
self.mp3_player_folder = self.get_from_parser( parser, 'mp3_player_folder', '/media/usbdisk')
self.only_sync_not_played = self.get_boolean_from_parser(parser, 'only_sync_not_played', default=False)
else:
log( 'config file %s has no section %s', fn, gpodderconf_section)
if not self.proxy_use_environment:
self.http_proxy = http.strip()
self.ftp_proxy = ftp.strip()
if app.strip():
self.open_app = app.strip()
else:
self.open_app = 'gnome-open'
if opml_url.strip():
self.opml_url = opml_url.strip()
else:
self.opml_url = default_opml_directory
except:
# TODO: well, well.. (http + ftp?)
self.open_app = 'gnome-open'
self.ipod_mount = '/media/ipod'
self.device_type = 'none'
self.main_window_width = 600
self.main_window_height = 450
self.main_window_x = 0
self.main_window_y = 0
self.paned_position = 150
self.mp3_player_folder = '/media/usbdisk'
self.opml_url = default_opml_directory
self.downloaddir = expanduser('~/gpodder-downloads')
self.torrentdir = expanduser('~/gpodder-downloads/torrents')
self.use_gnome_bittorrent = True
if was_oldstyle:
self.saveConfig()
def playback_episode( self, channel, episode):
self.history_mark_played( episode.url)
filename = episode.local_filename()
command_line = shlex.split( dotdesktop_command( self.open_app, filename).encode('utf-8'))
command_line = shlex.split( util.format_desktop_command( self.config.player, filename).encode('utf-8'))
log( 'Command line: [ %s ]', ', '.join( [ '"%s"' % p for p in command_line ]), sender = self)
try:
Popen( command_line)
subprocess.Popen( command_line)
except:
return ( False, command_line[0] )
return ( True, command_line[0] )
def open_folder( self, folder):
try:
Popen( [ 'xdg-open', folder ])
subprocess.Popen( [ 'xdg-open', folder ])
# FIXME: Win32-specific "open" code needed here
# as fallback when xdg-open not available
except:
@ -396,13 +160,13 @@ class gPodderLibClass( object):
def image_download_thread( self, url, callback_pixbuf = None, callback_status = None, callback_finished = None, cover_file = None):
if callback_status != None:
gobject.idle_add( callback_status, _('Downloading channel cover...'))
pixbuf = PixbufLoader()
pixbuf = gtk.gdk.PixbufLoader()
if cover_file == None:
log( 'Downloading %s', url)
pixbuf.write( urllib.urlopen(url).read())
if cover_file != None and not exists( cover_file):
if cover_file != None and not os.path.exists( cover_file):
log( 'Downloading cover to %s', cover_file)
cachefile = open( cover_file, "w")
cachefile.write( urllib.urlopen(url).read())
@ -445,20 +209,20 @@ class gPodderLibClass( object):
def invoke_torrent( self, url, torrent_filename, target_filename):
self.history_mark_played( url)
if self.use_gnome_bittorrent:
command = 'gnome-btdownload "%s" --saveas "%s"' % ( torrent_filename, join( self.torrentdir, target_filename))
if self.config.use_gnome_bittorrent:
command = 'gnome-btdownload "%s" --saveas "%s"' % ( torrent_filename, os.path.join( self.config.bittorrent_dir, target_filename))
log( command, sender = self)
system( '%s &' % command)
os.system( '%s &' % command)
else:
# Simply copy the .torrent with a suitable name
try:
target_filename = join( self.torrentdir, splitext( target_filename)[0] + '.torrent')
target_filename = os.path.join( self.config.bittorrent_dir, os.path.splitext( target_filename)[0] + '.torrent')
shutil.copyfile( torrent_filename, target_filename)
except:
log( 'Torrent copy failed: %s => %s.', torrent_filename, target_filename)
class DownloadHistory( ListType):
class HistoryStore( types.ListType):
def __init__( self, filename):
self.filename = filename
try:
@ -480,7 +244,7 @@ class DownloadHistory( ListType):
def add_item( self, data, autosave = True):
affected = 0
if data and type( data) is ListType:
if data and type( data) is types.ListType:
# Support passing a list of urls to this function
for url in data:
affected = affected + self.add_item( url, autosave = False)
@ -497,7 +261,7 @@ class DownloadHistory( ListType):
def del_item( self, data, autosave = True):
affected = 0
if data and type( data) is ListType:
if data and type( data) is types.ListType:
# Support passing a list of urls to this function
for url in data:
affected = affected + self.del_item( url, autosave = False)
@ -513,6 +277,3 @@ class DownloadHistory( ListType):
return affected
class PlaybackHistory( DownloadHistory):
pass

View File

@ -201,7 +201,7 @@ class gPodder_iPodSync( gPodderSyncMethod):
if not ipod_supported():
log( '(ipodsync) iPod functions not supported. (libgpod + eyed3 needed)')
gl = libgpodder.gPodderLib()
self.ipod_mount = gl.ipod_mount
self.ipod_mount = gl.config.ipod_mount
gPodderSyncMethod.__init__( self, callback_progress, callback_status, callback_done)
def open( self):
@ -507,7 +507,7 @@ class gPodder_FSSync( gPodderSyncMethod):
def __init__( self, callback_progress = None, callback_status = None, callback_done = None):
gl = libgpodder.gPodderLib()
self.destination = gl.mp3_player_folder
self.destination = gl.config.mp3_player_folder
gPodderSyncMethod.__init__( self, callback_progress, callback_status, callback_done)
self.can_cancel = True

View File

@ -109,25 +109,3 @@ class UserAppsReader(object):
return result
# end of UserAppsReader
def dotdesktop_command( command, filename):
# the following flags are specified in the FDO standards for ".desktop" files:
# http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-0.9.4.html
if command.find( '%U') != -1:
# A list of URLs. (we only need one, anyway..)
return command.replace( '%U', ( '"file://%s"' % (filename) ) )
if command.find( '%u') != -1:
# A single URL.
return command.replace( '%u', ( '"file://%s"' % (filename) ) )
if command.find( '%F') != -1:
# A list of files. (we only need one...)
return command.replace( '%F', filename )
if command.find( '%f') != -1:
# A single file name, even if multiple files are selected.
return command.replace( '%f', filename )
# default known-good variant: 1st parameter = filename
return '%s "%s"' % ( command, filename )
# end dotdesktop_command

View File

@ -51,6 +51,7 @@ import sys
import urllib
import urlparse
import time
import threading
from datetime import datetime
@ -74,6 +75,8 @@ import string
import shelve
global_lock = threading.RLock()
class ChannelSettings(object):
storage = shelve.open( libgpodder.gPodderLib().channel_settings_file)
@ -257,7 +260,7 @@ class podcastChannel(ListType):
gl = libgpodder.gPodderLib()
if not last_pubdate:
return self[0:min(len(self),gl.default_new)]
return self[0:min(len(self),gl.config.default_new)]
new_episodes = []
@ -290,7 +293,7 @@ class podcastChannel(ListType):
def addDownloadedItem( self, item):
# no multithreaded access
libgpodder.getLock()
global_lock.acquire()
downloaded_episodes = self.load_downloaded_episodes()
already_in_list = item.url in [ episode.url for episode in downloaded_episodes ]
@ -301,7 +304,7 @@ class podcastChannel(ListType):
self.save_downloaded_episodes( downloaded_episodes)
# Update metadata on file (if possible and wanted)
if libgpodder.gPodderLib().update_tags and tagging_supported():
if libgpodder.gPodderLib().config.update_tags and tagging_supported():
filename = item.local_filename()
try:
update_metadata_on_file( filename, title = item.title, artist = self.title)
@ -315,7 +318,7 @@ class podcastChannel(ListType):
destination_filename = util.torrent_filename( torrent_filename)
libgpodder.gPodderLib().invoke_torrent( item.url, torrent_filename, destination_filename)
libgpodder.releaseLock()
global_lock.release()
return not already_in_list
def is_played(self, item):
@ -439,7 +442,7 @@ class podcastChannel(ListType):
cover_file = property(fget=get_cover_file)
def delete_episode_by_url(self, url):
libgpodder.getLock()
global_lock.acquire()
downloaded_episodes = self.load_downloaded_episodes()
for episode in self.get_all_episodes():
@ -449,7 +452,7 @@ class podcastChannel(ListType):
downloaded_episodes.remove( episode)
self.save_downloaded_episodes( downloaded_episodes)
libgpodder.releaseLock()
global_lock.release()
class podcastItem(object):
"""holds data for one object in a channel"""
@ -523,8 +526,8 @@ class podcastItem(object):
return os.path.join( self.channel.save_dir, md5.new( self.url).hexdigest() + extension)
def sync_filename( self):
if libgpodder.gPodderLib().custom_sync_name_enabled:
return util.object_string_formatter( libgpodder.gPodderLib().custom_sync_name, episode = self, channel = self.channel)
if libgpodder.gPodderLib().config.custom_sync_name_enabled:
return util.object_string_formatter( libgpodder.gPodderLib().config.custom_sync_name, episode = self, channel = self.channel)
else:
return self.title

View File

@ -44,7 +44,7 @@ class DownloadStatusManager( object):
self.last_progress_status = ( 0, 0 )
self.max_downloads = libgpodder.gPodderLib().max_downloads
self.max_downloads = libgpodder.gPodderLib().config.max_downloads
self.semaphore = threading.Semaphore( self.max_downloads)
self.tree_model = gtk.ListStore( *self.COLUMN_TYPES)
@ -87,16 +87,16 @@ class DownloadStatusManager( object):
self.last_progress_status = now
def s_acquire( self):
if not libgpodder.gPodderLib().max_downloads_enabled:
if not libgpodder.gPodderLib().config.max_downloads_enabled:
return False
# Release queue slots if user has enabled more slots
while self.max_downloads < libgpodder.gPodderLib().max_downloads:
while self.max_downloads < libgpodder.gPodderLib().config.max_downloads:
self.semaphore.release()
self.max_downloads += 1
# Acquire queue slots if user has decreased the slots
while self.max_downloads > libgpodder.gPodderLib().max_downloads:
while self.max_downloads > libgpodder.gPodderLib().config.max_downloads:
self.semaphore.acquire()
self.max_downloads -= 1

View File

@ -362,3 +362,27 @@ def object_string_formatter( s, **kwargs):
return result
def format_desktop_command( command, filename):
"""
Formats a command template from the "Exec=" line of a .desktop
file to a string that can be invoked in a shell.
Handled format strings: %U, %u, %F, %f and a fallback that
appends the filename as first parameter of the command.
See http://standards.freedesktop.org/desktop-entry-spec/1.0/ar01s06.html
"""
items = {
'%U': 'file://%s' % filename,
'%u': 'file://%s' % filename,
'%F': filename,
'%f': filename,
}
for key, value in items.items():
if command.find( key) >= 0:
return command.replace( key, value)
return '%s "%s"' % ( command, filename )