playback status tracking + ipod playcount sync

git-svn-id: svn://svn.berlios.de/gpodder/trunk@302 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
Thomas Perl 2007-04-03 11:21:12 +00:00
parent d71d72761b
commit 7bed9a8aae
8 changed files with 137 additions and 21 deletions

View File

@ -1,3 +1,26 @@
Tue, 3 Apr 2007 13:14:02 +0200 <thp@perli.net>
* src/gpodder/liblogger.py: Enhance the log() function to accept a
"sender" keyword argument that contains the object tha sends the
message; the message will be transformed into "(objectclass) message"
* src/gpodder/libpodcasts.py: Add "Played" icon to the model for
played status display; use gtk.STOCK_NEW instead of "document-new"
for new items; mark the latest three items as new if no episodes
have been downloaded so far from this channel; generalize the
DownloadHistory class and derive a PlaybackHistory class from it
* src/gpodder/libgpodder.py: Make use of PlaybackHistory; add config
option "show_played" (disable/enable "Played" column in treeview);
replace openFilename() function with playback_episode() function
that also updates the playback history accordingly
* src/gpodder/libipodsync.py: Synchronize playback state ("blue dot")
to iPod when syncing (also update playback state when episodes is
already on iPod); fixed a small bug that didn't set podcast flags
* src/gpodder/gpodder.py: Make use of new playback_episode function;
add support for the "Played" column (+config disable/enable)
* data/gpodder.glade: Add GtkCheckButton for displaying/hiding the
"Played" column in the available episodes list
* TODO: Updated TODO list (display played episodes task is done now;
mark tag update and search box as partially implemented)
Tue, 3 Apr 2007 08:18:28 +0200 <thp@perli.net>
* src/gpodder/libpodcasts.py: Remove background color code in model;
add icons; add new get_file_type() function to check if an episode

6
TODO
View File

@ -9,11 +9,9 @@
* Utilize DesktopEntry in python-xdg for libplayers
* Now for a genuine feature request: a search box?
-> partially implemented (search-as-you-type in available episodes)
(Michel Salim; michel.salim gmail.com)
* Finally, could it mark via a different color code when a podcast
has been played. (John Connor; johnc.connor gmail.com)
* My second suggestion is to include a systray icon similar to the ones
many music players have. It would be great if I could hide/show the
gpodder window by clicking on it. Getting current download status on
@ -52,6 +50,8 @@
-> Only populate when MP3 files don't yet have tags
-> Always populate; overwrite old tags if existing
(partially implemented already)
(Thanks to ubunt2@gmail.com for suggesting the metadata populate feat.)
* in libipodsync.py: use mime magic instead of extension-based

View File

@ -1996,6 +1996,36 @@
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator3">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="showplayed">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Display "Played" status in episode list</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>

View File

@ -120,6 +120,11 @@ class Gpodder(SimpleGladeApp):
iconcolumn = gtk.TreeViewColumn( _("Status"), iconcell)
iconcolumn.add_attribute( iconcell, "icon-name", 4)
playedcell = gtk.CellRendererPixbuf()
playedcolumn = gtk.TreeViewColumn( _("Played"), playedcell)
playedcolumn.add_attribute( playedcell, "icon-name", 8)
self.played_column = playedcolumn
namecell = gtk.CellRendererText()
#namecell.set_property('ellipsize', pango.ELLIPSIZE_END)
namecolumn = gtk.TreeViewColumn( _("Episode"), namecell, text=1)
@ -135,7 +140,7 @@ class Gpodder(SimpleGladeApp):
desccell.set_property('ellipsize', pango.ELLIPSIZE_END)
desccolumn = gtk.TreeViewColumn( _("Description"), desccell, text=6)
for itemcolumn in ( iconcolumn, namecolumn, sizecolumn, releasecolumn, desccolumn ):
for itemcolumn in ( iconcolumn, playedcolumn, namecolumn, sizecolumn, releasecolumn, desccolumn ):
itemcolumn.set_resizable( True)
itemcolumn.set_reorderable( True)
self.treeAvailable.append_column( itemcolumn)
@ -192,6 +197,10 @@ class Gpodder(SimpleGladeApp):
#-- Gpodder.new }
#-- Gpodder custom methods {
def playback_episode( self, current_channel, current_podcast):
gPodderLib().playback_episode( current_channel, current_podcast)
self.updateTreeView()
def treeAvailable_search_equal( self, model, column, key, iter, data = None):
if model == None:
return True
@ -264,6 +273,9 @@ class Gpodder(SimpleGladeApp):
pass
def updateTreeView( self):
gl = gPodderLib()
self.played_column.set_visible( gl.show_played)
rect = self.treeAvailable.get_visible_rect()
if self.channels:
self.treeAvailable.set_model( self.active_channel.items_liststore( downloading_callback = self.download_status_manager.is_download_in_progress))
@ -377,6 +389,7 @@ class Gpodder(SimpleGladeApp):
sync.sync_channel( self.active_channel, episodes)
sync.close( success = not sync.cancelled)
gobject.idle_add( self.updateTreeView)
def ipod_cleanup_proc( self, sync):
if not sync.open():
@ -386,6 +399,7 @@ class Gpodder(SimpleGladeApp):
sync.clean_playlist()
sync.close( success = not sync.cancelled, cleaned = True)
gobject.idle_add( self.updateTreeView)
def update_feed_cache_callback( self, label, progressbar, position, count):
title = _('Please wait...')
@ -474,7 +488,7 @@ class Gpodder(SimpleGladeApp):
if current_channel.addDownloadedItem( current_podcast):
self.ldb.clear_cache()
# open the file now
gPodderLib().openFilename( filename)
self.playback_episode( current_channel, current_podcast)
return
if widget.get_name() == 'treeAvailable':
@ -482,7 +496,7 @@ class Gpodder(SimpleGladeApp):
gpe.set_episode( current_podcast, current_channel)
if os.path.exists( filename):
gpe.set_play_callback( lambda: gPodderLib().openFilename( filename))
gpe.set_play_callback( lambda: self.playback_episode( current_channel, current_podcast))
else:
gpe.set_download_callback( lambda: self.download_podcast_by_url( url, want_message_dialog, None))
@ -1141,6 +1155,7 @@ class Gpodderproperties(SimpleGladeApp):
self.chooserDownloadTo.set_filename( gl.downloaddir)
self.updateonstartup.set_active(gl.update_on_startup)
self.downloadnew.set_active(gl.download_after_update)
self.showplayed.set_active(gl.show_played)
if tagging_supported():
self.updatetags.set_active(gl.update_tags)
else:
@ -1347,6 +1362,7 @@ class Gpodderproperties(SimpleGladeApp):
gl.update_on_startup = self.updateonstartup.get_active()
gl.download_after_update = self.downloadnew.get_active()
gl.show_played = self.showplayed.get_active()
gl.update_tags = self.updatetags.get_active()
device_type = self.comboboxDeviceType.get_active()
if device_type == 0:

View File

@ -69,6 +69,7 @@ from stat import ST_MODE
from librssreader import rssReader
from libpodcasts import podcastChannel
from libpodcasts import DownloadHistory
from libpodcasts import PlaybackHistory
from libplayers import dotdesktop_command
from gtk.gdk import PixbufLoader
@ -123,6 +124,7 @@ class gPodderLibClass( object):
self.opml_url = ""
self.update_on_startup = False
self.download_after_update = False
self.show_played = False
self.update_tags = False
self.desktop_link = _("gPodder downloads")
self.device_type = None
@ -132,6 +134,7 @@ class gPodderLibClass( object):
self.main_window_y = 0
self.mp3_player_folder = ""
self.__download_history = DownloadHistory( self.get_download_history_filename())
self.__playback_history = PlaybackHistory( self.get_playback_history_filename())
self.loadConfig()
def createIfNecessary( self, path):
@ -154,6 +157,9 @@ class gPodderLibClass( object):
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 propertiesChanged( self):
# set new environment variables for subprocesses to use,
# but only if we are not told to passthru the env vars
@ -192,6 +198,7 @@ class gPodderLibClass( object):
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, 'show_played', self.show_played)
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)
@ -253,7 +260,10 @@ class gPodderLibClass( object):
downloaddir = property(fget=get_download_dir,fset=set_download_dir)
def history_mark_downloaded( self, url):
self.__download_history.mark_downloaded( url)
self.__download_history.add_item( url)
def history_mark_played( self, url):
self.__playback_history.add_item( url)
def can_write_directory( self, directory):
return isdir( directory) and access( directory, W_OK)
@ -261,6 +271,9 @@ class gPodderLibClass( object):
def history_is_downloaded( self, url):
return (url in self.__download_history)
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)
@ -314,6 +327,7 @@ class gPodderLibClass( object):
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.show_played = self.get_boolean_from_parser(parser, 'show_played', default=False)
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.device_type = self.get_from_parser( parser, 'device_type', 'none')
@ -350,7 +364,10 @@ class gPodderLibClass( object):
if was_oldstyle:
self.saveConfig()
def openFilename( self, filename):
def playback_episode( self, channel, episode):
self.history_mark_played( episode.url)
filename = channel.getPodcastFilename( episode.url)
log( 'Opening %s (with %s)', filename, self.open_app)
# use libplayers to create a commandline out of open_app plus filename, then exec in background ('&')

View File

@ -293,7 +293,16 @@ class gPodder_iPodSync( gPodderSyncMethod):
return False
for track in gpod.sw_get_playlist_tracks( pl):
if episode.title == track.title and channel.title == track.album:
log( '(ipodsync) Already on iPod: %s (from %s)', episode.title, track.title)
gl = libgpodder.gPodderLib()
# Mark as played locally if played on iPod
if track.playcount > 0:
log( 'Episode has been played %d times on iPod: %s', track.playcount, episode.title, sender = self)
gl.history_mark_played( episode.url)
# Mark as played on iPod if played locally (and set podcast flags)
self.set_podcast_flags( track, episode)
return True
return False
@ -305,12 +314,17 @@ class gPodder_iPodSync( gPodderSyncMethod):
log( '(ipodsync) Trying to remove: %s', track.title)
self.remove_from_ipod( track, [ self.pl_podcasts ])
def set_podcast_flags( self, track):
def set_podcast_flags( self, track, episode):
if not ipod_supported():
return False
try:
# Add blue bullet next to unplayed tracks on 5G iPods
track.mark_unplayed = 0x02
# (only if the podcast has not been played locally already
gl = libgpodder.gPodderLib()
if gl.history_is_played( episode.url):
track.mark_unplayed = 0x01
else:
track.mark_unplayed = 0x02
# Podcast flags (for new iPods?)
track.remember_playback_position = 0x01
@ -408,8 +422,7 @@ class gPodder_iPodSync( gPodderSyncMethod):
track = gpod.itdb_track_new()
track.artist = str(channel.title)
if channel.is_music_channel:
self.set_podcast_flags( track)
self.set_podcast_flags( track, episode)
# Add release time to track if pubDate is parseable
ipod_date = email.Utils.parsedate(episode.pubDate)

View File

@ -36,7 +36,9 @@ def enable_verbose():
write_to_stdout = True
def log( message, *args):
def log( message, *args, **kwargs):
if 'sender' in kwargs:
message = '(%s) %s' % ( kwargs['sender'].__class__.__name__, message )
if write_to_stdout:
print message % args

View File

@ -239,13 +239,20 @@ class podcastChannel(ListType):
the URL of the episodes and returns True if the episode is currently
being downloaded and False otherwise.
"""
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
gl = libgpodder.gPodderLib()
last_pubdate = self.newest_pubdate_downloaded()
index = 1
for item in self.get_all_episodes():
played_icon = None
if self.is_downloaded( item) and want_color:
if libgpodder.gPodderLib().history_is_played( item.url):
played_icon = gtk.STOCK_YES
#else:
# played_icon = gtk.STOCK_NO
file_type = self.get_file_type( item)
if file_type == 'audio':
status_icon = 'audio-x-generic'
@ -258,7 +265,9 @@ class podcastChannel(ListType):
elif libgpodder.gPodderLib().history_is_downloaded( item.url) and want_color:
status_icon = gtk.STOCK_DELETE
elif last_pubdate and item.compare_pubdate( last_pubdate) >= 0:
status_icon = 'document-new'
status_icon = gtk.STOCK_NEW
elif not last_pubdate and index <= 3:
status_icon = gtk.STOCK_NEW
else:
status_icon = None
new_iter = new_model.append()
@ -270,6 +279,8 @@ class podcastChannel(ListType):
new_model.set( new_iter, 5, item.cute_pubdate())
new_model.set( new_iter, 6, item.one_line_description())
new_model.set( new_iter, 7, item.description)
new_model.set( new_iter, 8, played_icon)
index += 1
return new_model
@ -534,7 +545,7 @@ class DownloadHistory( ListType):
try:
self.read_from_file()
except:
log( '(DownloadHistory) Creating new history list.')
log( 'Creating new history list.', sender = self)
def read_from_file( self):
for line in open( self.filename, 'r'):
@ -546,17 +557,17 @@ class DownloadHistory( ListType):
for url in self:
fp.write( url + "\n")
fp.close()
log( '(DownloadHistory) Wrote %d history entries.', len( self))
log( 'Wrote %d history entries.', len( self), sender = self)
def mark_downloaded( self, data, autosave = True):
def add_item( self, data, autosave = True):
affected = 0
if data and type( data) is ListType:
# Support passing a list of urls to this function
for url in data:
affected = affected + self.mark_downloaded( url, autosave = False)
affected = affected + self.add_item( url, autosave = False)
else:
if data not in self:
log( '(DownloadHistory) Marking as downloaded: %s', data)
log( 'Adding: %s', data, sender = self)
self.append( data)
affected = affected + 1
@ -566,6 +577,10 @@ class DownloadHistory( ListType):
return affected
class PlaybackHistory( DownloadHistory):
pass
def channelsToModel( channels):
new_model = gtk.ListStore( gobject.TYPE_STRING, gobject.TYPE_STRING)