Refactor libpodcasts; fix episode dialog
Refactor libpodcasts: * Rename podcastChannel to PodcastChannel * Rename podcastItem to PodcastEpisode * Add a new base class with common functions for PodcastChannel/PodcastEpisode * Remove unneeded / orphaned functions and properties Fix the buttons in the episode shownotes dialog when a new episode finishes downloading by reloading the filename from the database (so we can display the play button).
This commit is contained in:
parent
73661afead
commit
0cafc12941
|
@ -27,7 +27,7 @@ from gpodder.dbsqlite import db
|
|||
from libpodcasts import load_channels
|
||||
from libpodcasts import update_channels
|
||||
from libpodcasts import save_channels
|
||||
from libpodcasts import podcastChannel
|
||||
from libpodcasts import PodcastChannel
|
||||
|
||||
import time
|
||||
|
||||
|
@ -52,7 +52,7 @@ def add_channel( url):
|
|||
return
|
||||
|
||||
try:
|
||||
channel = podcastChannel.load(url, create=True)
|
||||
channel = PodcastChannel.load(url, create=True)
|
||||
except:
|
||||
msg( 'error', _('Could not load feed from URL: %s'), urllib.unquote( url))
|
||||
return
|
||||
|
|
|
@ -434,9 +434,11 @@ class Storage(object):
|
|||
|
||||
def load_episode(self, url, factory=None):
|
||||
self.log("load_episode(%s)", url)
|
||||
list = self.__read_episodes(factory = factory, where = " WHERE url = ?", params = (url, ))
|
||||
if len(list):
|
||||
list = self.__read_episodes(factory=factory, where=' WHERE url=? LIMIT ?', params=(url, 1))
|
||||
if list:
|
||||
return list[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def save_episode(self, e, bulk=False):
|
||||
if not e.guid:
|
||||
|
|
|
@ -63,9 +63,8 @@ except Exception, exc:
|
|||
log('Warning: This probably means your PyGTK installation is too old!')
|
||||
have_trayicon = False
|
||||
|
||||
from libpodcasts import podcastChannel
|
||||
from libpodcasts import PodcastChannel
|
||||
from libpodcasts import LocalDBReader
|
||||
from libpodcasts import podcastItem
|
||||
from libpodcasts import channels_to_model
|
||||
from libpodcasts import update_channel_model_by_iter
|
||||
from libpodcasts import load_channels
|
||||
|
@ -1405,7 +1404,7 @@ class gPodder(GladeWidget, dbus.service.Object):
|
|||
for path in paths:
|
||||
url = model.get_value( model.get_iter( path), 0)
|
||||
|
||||
episode = podcastItem.load(url, self.active_channel)
|
||||
episode = self.active_channel.find_episode(url)
|
||||
|
||||
if episode.file_type() not in ('audio', 'video'):
|
||||
open_instead_of_play = True
|
||||
|
@ -1677,11 +1676,11 @@ class gPodder(GladeWidget, dbus.service.Object):
|
|||
log( 'Adding new channel: %s', url)
|
||||
channel = error = None
|
||||
try:
|
||||
channel = podcastChannel.load(url=url, create=True, authentication_tokens=authentication_tokens)
|
||||
channel = PodcastChannel.load(url=url, create=True, authentication_tokens=authentication_tokens)
|
||||
except HTTPAuthError, e:
|
||||
error = e
|
||||
except Exception, e:
|
||||
log('Error in podcastChannel.load(%s): %s', url, e, traceback=True, sender=self)
|
||||
log('Error in PodcastChannel.load(%s): %s', url, e, traceback=True, sender=self)
|
||||
|
||||
util.idle_add( callback, channel, url, error, *callback_args )
|
||||
|
||||
|
@ -3658,6 +3657,9 @@ class gPodderEpisode(GladeWidget):
|
|||
|
||||
def on_download_status_changed(self, episode_urls, channel_urls):
|
||||
if self.gPodderEpisode.get_property('visible'):
|
||||
# Reload the episode from the database, so a newly-set local_filename
|
||||
# as a result of a download gets updated in the episode object
|
||||
self.episode.reload_from_db()
|
||||
self.hide_show_widgets()
|
||||
else:
|
||||
log('download status changed, but not visible', sender=self)
|
||||
|
|
|
@ -78,9 +78,35 @@ else:
|
|||
|
||||
class HTTPAuthError(Exception): pass
|
||||
|
||||
class podcastChannel(object):
|
||||
|
||||
class PodcastModelObject(object):
|
||||
"""
|
||||
A generic base class for our podcast model providing common helper
|
||||
and utility functions.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def create_from_dict(cls, d, *args):
|
||||
"""
|
||||
Create a new object, passing "args" to the constructor
|
||||
and then updating the object with the values from "d".
|
||||
"""
|
||||
o = cls(*args)
|
||||
o.update_from_dict(d)
|
||||
return o
|
||||
|
||||
def update_from_dict(self, d):
|
||||
"""
|
||||
Updates the attributes of this object with values from the
|
||||
dictionary "d" by using the keys found in "d".
|
||||
"""
|
||||
for k in d:
|
||||
if hasattr(self, k):
|
||||
setattr(self, k, d[k])
|
||||
|
||||
|
||||
class PodcastChannel(PodcastModelObject):
|
||||
"""holds data for a complete channel"""
|
||||
SETTINGS = ('sync_to_devices', 'device_playlist_name','override_title','username','password')
|
||||
MAX_FOLDERNAME_LENGTH = 150
|
||||
icon_cache = {}
|
||||
|
||||
|
@ -91,11 +117,11 @@ class podcastChannel(object):
|
|||
if isinstance(url, unicode):
|
||||
url = url.encode('utf-8')
|
||||
|
||||
tmp = db.load_channels(factory=lambda d: cls.create_from_dict(d), url=url)
|
||||
tmp = db.load_channels(factory=cls.create_from_dict, url=url)
|
||||
if len(tmp):
|
||||
return tmp[0]
|
||||
elif create:
|
||||
tmp = podcastChannel(url)
|
||||
tmp = PodcastChannel(url)
|
||||
if authentication_tokens is not None:
|
||||
tmp.username = authentication_tokens[0]
|
||||
tmp.password = authentication_tokens[1]
|
||||
|
@ -109,13 +135,15 @@ class podcastChannel(object):
|
|||
db.force_last_new(tmp)
|
||||
return tmp
|
||||
|
||||
@staticmethod
|
||||
def create_from_dict(d):
|
||||
c = podcastChannel()
|
||||
for key in d:
|
||||
if hasattr(c, key):
|
||||
setattr(c, key, d[key])
|
||||
return c
|
||||
def episode_factory(self, d):
|
||||
"""
|
||||
This function takes a dictionary containing key-value pairs for
|
||||
episodes and returns a new PodcastEpisode object that is connected
|
||||
to this PodcastChannel object.
|
||||
|
||||
Returns: A new PodcastEpisode object
|
||||
"""
|
||||
return PodcastEpisode.create_from_dict(d, self)
|
||||
|
||||
def update(self):
|
||||
(updated, c) = self.fc.fetch(self.url, self)
|
||||
|
@ -201,7 +229,7 @@ class podcastChannel(object):
|
|||
episode = None
|
||||
|
||||
try:
|
||||
episode = podcastItem.from_feedparser_entry(entry, self)
|
||||
episode = PodcastEpisode.from_feedparser_entry(entry, self)
|
||||
except Exception, e:
|
||||
log('Cannot instantiate episode "%s": %s. Skipping.', entry.get('id', '(no id available)'), e, sender=self, traceback=True)
|
||||
|
||||
|
@ -325,13 +353,10 @@ class podcastChannel(object):
|
|||
self.override_title = ''
|
||||
|
||||
def get_downloaded_episodes(self):
|
||||
return db.load_episodes(self, factory=lambda c: podcastItem.create_from_dict(c, self), state=db.STATE_DOWNLOADED)
|
||||
|
||||
def save_settings(self):
|
||||
db.save_channel(self)
|
||||
return db.load_episodes(self, factory=self.episode_factory, state=db.STATE_DOWNLOADED)
|
||||
|
||||
def get_new_episodes( self):
|
||||
return [episode for episode in db.load_episodes(self, factory=lambda x: podcastItem.create_from_dict(x, self)) if episode.state == db.STATE_NORMAL and not episode.is_played and not services.download_status_manager.is_download_in_progress(episode.url)]
|
||||
return [episode for episode in db.load_episodes(self, factory=self.episode_factory) if episode.state == db.STATE_NORMAL and not episode.is_played and not services.download_status_manager.is_download_in_progress(episode.url)]
|
||||
|
||||
def update_m3u_playlist(self):
|
||||
if gl.config.create_m3u_playlists:
|
||||
|
@ -374,7 +399,7 @@ class podcastChannel(object):
|
|||
self.update_m3u_playlist()
|
||||
|
||||
def get_all_episodes(self):
|
||||
return db.load_episodes(self, factory = lambda d: podcastItem.create_from_dict(d, self))
|
||||
return db.load_episodes(self, factory=self.episode_factory)
|
||||
|
||||
def iter_set_downloading_columns( self, model, iter, episode=None):
|
||||
global ICON_AUDIO_FILE, ICON_VIDEO_FILE
|
||||
|
@ -382,7 +407,7 @@ class podcastChannel(object):
|
|||
|
||||
if episode is None:
|
||||
url = model.get_value( iter, 0)
|
||||
episode = db.load_episode(url, factory=lambda x: podcastItem.create_from_dict(x, self))
|
||||
episode = db.load_episode(url, factory=self.episode_factory)
|
||||
else:
|
||||
url = episode.url
|
||||
|
||||
|
@ -448,7 +473,7 @@ class podcastChannel(object):
|
|||
return (new_model, urls)
|
||||
|
||||
def find_episode( self, url):
|
||||
return db.load_episode(url, factory=lambda x: podcastItem.create_from_dict(x, self))
|
||||
return db.load_episode(url, factory=self.episode_factory)
|
||||
|
||||
@classmethod
|
||||
def find_unique_folder_name(cls, foldername):
|
||||
|
@ -533,7 +558,7 @@ class podcastChannel(object):
|
|||
cover_file = property(fget=get_cover_file)
|
||||
|
||||
def delete_episode_by_url(self, url):
|
||||
episode = db.load_episode(url, lambda c: podcastItem.create_from_dict(c, self))
|
||||
episode = db.load_episode(url, factory=self.episode_factory)
|
||||
|
||||
if episode is not None:
|
||||
filename = episode.local_filename(create=False)
|
||||
|
@ -546,23 +571,27 @@ class podcastChannel(object):
|
|||
self.update_m3u_playlist()
|
||||
|
||||
|
||||
class podcastItem(object):
|
||||
class PodcastEpisode(PodcastModelObject):
|
||||
"""holds data for one object in a channel"""
|
||||
MAX_FILENAME_LENGTH = 200
|
||||
|
||||
@staticmethod
|
||||
def load(url, channel):
|
||||
e = podcastItem(channel)
|
||||
d = db.load_episode(url)
|
||||
def reload_from_db(self):
|
||||
"""
|
||||
Re-reads all episode details for this object from the
|
||||
database and updates this object accordingly. Can be
|
||||
used to refresh existing objects when the database has
|
||||
been updated (e.g. the filename has been set after a
|
||||
download where it was not set before the download)
|
||||
"""
|
||||
d = db.load_episode(self.url)
|
||||
if d is not None:
|
||||
for k, v in d.iteritems():
|
||||
if hasattr(e, k):
|
||||
setattr(e, k, v)
|
||||
return e
|
||||
self.update_from_dict(d)
|
||||
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def from_feedparser_entry( entry, channel):
|
||||
episode = podcastItem( channel)
|
||||
episode = PodcastEpisode( channel)
|
||||
|
||||
episode.title = entry.get( 'title', util.get_first_line( util.remove_html_tags( entry.get( 'summary', ''))))
|
||||
episode.link = entry.get( 'link', '')
|
||||
|
@ -673,14 +702,6 @@ class podcastItem(object):
|
|||
self.save()
|
||||
db.commit()
|
||||
|
||||
@staticmethod
|
||||
def create_from_dict(d, channel):
|
||||
e = podcastItem(channel)
|
||||
for key in d:
|
||||
if hasattr(e, key):
|
||||
setattr(e, key, d[key])
|
||||
return e
|
||||
|
||||
@property
|
||||
def title_and_description(self):
|
||||
"""
|
||||
|
@ -1010,7 +1031,7 @@ def channels_to_model(channels, color_dict, cover_cache=None, max_width=0, max_h
|
|||
|
||||
|
||||
def load_channels():
|
||||
return db.load_channels(lambda d: podcastChannel.create_from_dict(d))
|
||||
return db.load_channels(factory=PodcastChannel.create_from_dict)
|
||||
|
||||
def update_channels(callback_proc=None, callback_error=None, is_cancelled_cb=None):
|
||||
log('Updating channels....')
|
||||
|
@ -1054,7 +1075,7 @@ class LocalDBReader( object):
|
|||
return self.get_text( element.getElementsByTagName( name)[0].childNodes)
|
||||
|
||||
def get_episode_from_element( self, channel, element):
|
||||
episode = podcastItem( channel)
|
||||
episode = PodcastEpisode(channel)
|
||||
episode.title = self.get_text_by_first_node( element, 'title')
|
||||
episode.description = self.get_text_by_first_node( element, 'description')
|
||||
episode.url = self.get_text_by_first_node( element, 'url')
|
||||
|
@ -1109,7 +1130,7 @@ class LocalDBReader( object):
|
|||
|
||||
channel_element = rss.getElementsByTagName('channel')[0]
|
||||
|
||||
channel = podcastChannel( url = self.url)
|
||||
channel = PodcastChannel(url=self.url)
|
||||
channel.title = self.get_text_by_first_node( channel_element, 'title')
|
||||
channel.description = self.get_text_by_first_node( channel_element, 'description')
|
||||
channel.link = self.get_text_by_first_node( channel_element, 'link')
|
||||
|
|
|
@ -44,7 +44,7 @@ import os.path
|
|||
|
||||
class VCChannel(object):
|
||||
"""
|
||||
Fake podcastChannel-like object to allow
|
||||
Fake podcast channel-like object to allow
|
||||
opml's Exporter class to write an OPML file.
|
||||
"""
|
||||
def __init__(self, title, description, url):
|
||||
|
|
|
@ -28,7 +28,6 @@ import datetime
|
|||
import gpodder
|
||||
from gpodder.liblogger import log
|
||||
from gpodder.libgpodder import gl
|
||||
from gpodder.libpodcasts import podcastItem
|
||||
|
||||
try:
|
||||
import pynotify
|
||||
|
@ -386,7 +385,7 @@ class GPodderStatusIcon(gtk.StatusIcon):
|
|||
If the list is too long, it is cut and the string "x others episodes" is append
|
||||
|
||||
episode_list
|
||||
can be either a list containing podcastItem objects
|
||||
can be either a list containing episode objects
|
||||
or a list of strings of episode's title.
|
||||
|
||||
return
|
||||
|
@ -400,10 +399,10 @@ class GPodderStatusIcon(gtk.StatusIcon):
|
|||
if caption is not None:
|
||||
result.append('\n%s' % caption)
|
||||
for episode in episode_list[:min(len(episode_list),MAX_EPISODES)]:
|
||||
if isinstance(episode, podcastItem):
|
||||
episode_title = episode.title
|
||||
else:
|
||||
if type(episode) in (str, unicode):
|
||||
episode_title = episode
|
||||
else:
|
||||
episode_title = episode.title
|
||||
if len(episode_title) < MAX_TITLE_LENGTH:
|
||||
title = episode_title
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue