Selective iPod episode clean-up; "delete older than X days"

git-svn-id: svn://svn.berlios.de/gpodder/trunk@486 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
Thomas Perl 2007-12-10 08:41:17 +00:00
parent 2db760e08c
commit 8ba93845be
6 changed files with 134 additions and 45 deletions

View File

@ -1,3 +1,29 @@
Mon, 10 Dec 2007 09:33:26 +0100 <thp@perli.net>
Selective iPod episode clean-up; "delete older than X days"
* src/gpodder/config.py: Add episode_old_age configuration variable;
is an integer, defaults to 7. This is the number of days after which
an episode will be considered "old"
* src/gpodder/gui.py: iPod cleanup is now done selectively using the
episode selector dialog; the code has been split, because we do not
yet have an episode selector dialog for FS-based sync; add "Downloaded
x days ago" column to "Delete old episodes" dialog and a corresponding
button; call the callback even when no episodes are selected in
gPodderEpisodeSelector
* src/gpodder/libipodsync.py: Class variables of gPodder_iPodSync
moved to __init__ to be instance variables (this should fix a bug that
crops up now that we can selectively delete episodes); remove_tracks()
and clean_playlist() have been modified/added to support new episode
selector deletion code
* src/gpodder/libpodcasts.py: Move is_played() to podcastItem from
podcastChannel (it really belongs to the item; this makes code more
readable in other parts of the codebase); add age_in_days(), is_old(),
get_age_string() and age_prop to podcastItem
* src/gpodder/util.py: New function: file_modification_datetime();
returns a datetime.datetime instance of the MTIME (modification
timestamp) of the filename given as parameter or None if the filename
cannot be determined; this is used for the "old episodes" feature
Sun, 09 Dec 2007 16:45:11 +0100 <thp@perli.net>
Make has_converter() a bit more intelligent in detecting extensions

View File

@ -48,6 +48,7 @@ gPodderSettings = {
'limit_rate': ( bool, False ),
'limit_rate_value': ( float, 500.0 ),
'bittorrent_dir': ( str, os.path.expanduser( '~/gpodder-downloads/torrents') ),
'episode_old_age': ( int, 7 ),
# Boolean config flags
'update_on_startup': ( bool, False ),

View File

@ -678,15 +678,38 @@ class gPodder(GladeWidget):
gobject.idle_add( channel.update_model)
gobject.idle_add( self.updateComboBox)
def ipod_cleanup_callback(self, sync, tracks):
title = _('Delete podcasts on iPod?')
message = _('Do you really want to completely remove the selected episodes?')
if len(tracks) > 0 and self.show_confirmation(message, title):
sync.remove_tracks(tracks)
sync.close(success=not sync.cancelled, cleaned=True)
gobject.idle_add(self.updateTreeView)
def ipod_cleanup_proc( self, sync):
if not sync.open():
gobject.idle_add( self.show_message, message, title)
sync.close( success = False, access_error = True)
sync.close(success = False, access_error = True)
return False
tracklist = sync.clean_playlist()
if tracklist is not None:
remove_tracks_callback = lambda tracks: self.ipod_cleanup_callback(sync, tracks)
title = _('Remove podcasts from iPod')
instructions = _('Select the podcast episodes you want to remove from your iPod.')
gPodderEpisodeSelector( title = title, instructions = instructions, episodes = tracklist, \
stock_ok_button = gtk.STOCK_DELETE, callback = remove_tracks_callback)
else:
sync.close(success = not sync.cancelled, cleaned = True)
gobject.idle_add(self.updateTreeView)
def mp3player_cleanup_proc( self, sync):
if not sync.open():
sync.close(success = False, access_error = True)
return False
sync.clean_playlist()
sync.close( success = not sync.cancelled, cleaned = True)
gobject.idle_add( self.updateTreeView)
sync.close(success = not sync.cancelled, cleaned = True)
gobject.idle_add(self.updateTreeView)
def update_feed_cache_callback( self, label, progressbar, position, count):
if len(self.channels) > position:
@ -854,10 +877,13 @@ class gPodder(GladeWidget):
('filesize_prop', _('Size')),
('pubdate_prop', _('Released')),
('played_prop', _('Status')),
('age_prop', _('Downloaded')),
)
gl = gPodderLib()
selection_buttons = {
_('Select played'): lambda episode: episode.channel.is_played( episode),
_('Select played'): lambda episode: episode.is_played(),
_('Select older than %d days') % gl.config.episode_old_age: lambda episode: episode.is_old(),
}
instructions = _('Select the episodes you want to delete from your hard disk.')
@ -868,7 +894,7 @@ class gPodder(GladeWidget):
for episode in channel:
if episode.is_downloaded():
episodes.append( episode)
selected.append( channel.is_played( episode))
selected.append( episode.is_played())
gPodderEpisodeSelector( title = _('Remove old episodes'), instructions = instructions, \
episodes = episodes, selected = selected, columns = columns, \
@ -972,34 +998,23 @@ class gPodder(GladeWidget):
message = _('To use the synchronization feature, please configure your device in the preferences dialog first.')
self.show_message( message, title)
return
if gl.config.device_type == 'ipod' and not ipod_supported():
elif 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.config.device_type in [ 'ipod', 'filesystem' ]:
sync_class = None
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.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):
sync_class = gPodder_iPodSync
if not sync_class:
return
sync_win = gPodderSync()
sync = sync_class( callback_status = sync_win.set_status, callback_progress = sync_win.set_progress, callback_done = sync_win.close)
sync_win.set_sync_object( sync)
thread = Thread( target = self.ipod_cleanup_proc, args = ( sync, ))
elif 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_win = gPodderSync()
sync = gPodder_FSSync(callback_status=sync_win.set_status, callback_progress=sync_win.set_progress, callback_done=sync_win.close)
sync_win.set_sync_object(sync)
thread = Thread(target=self.mp3player_cleanup_proc, args=(sync,))
thread.start()
elif gl.config.device_type == 'ipod':
sync = gPodder_iPodSync()
thread = Thread(target=self.ipod_cleanup_proc, args=(sync,))
thread.start()
def update_item_device( self):
@ -2103,6 +2118,8 @@ class gPodderEpisodeSelector( GladeWidget):
def on_btnCancel_clicked( self, widget):
self.gPodderEpisodeSelector.destroy()
if self.callback is not None:
self.callback([])
def main():

View File

@ -146,7 +146,7 @@ class gPodderSyncMethod:
if self.cancelled:
return False
self.set_progress( pos, max)
if episode.is_downloaded() and episode.file_type() in ( 'audio', 'video' ) and (sync_played_episodes or not channel.is_played( episode)):
if episode.is_downloaded() and episode.file_type() in ( 'audio', 'video' ) and (sync_played_episodes or not episode.is_played()):
if not self.add_episode_from_channel( channel, episode):
return False
self.set_progress( pos, max)
@ -184,17 +184,15 @@ class gPodderSyncMethod:
class gPodder_iPodSync( gPodderSyncMethod):
itdb = None
ipod_mount = '' # mountpoint for ipod
playlist_name = 'gpodder' # name of playlist to sync to
pl_master = None
pl_podcasts = None
def __init__( self, callback_progress = None, callback_status = None, callback_done = None):
if not ipod_supported():
log( '(ipodsync) iPod functions not supported. (libgpod + eyed3 needed)')
gl = libgpodder.gPodderLib()
self.ipod_mount = gl.config.ipod_mount
self.itdb = None
self.playlist_name = 'gpodder' # name of playlist to sync to
self.pl_master = None
self.pl_podcasts = None
gPodderSyncMethod.__init__( self, callback_progress, callback_status, callback_done)
def open( self):
@ -295,13 +293,17 @@ class gPodder_iPodSync( gPodderSyncMethod):
return True
return False
def remove_tracks(self, tracklist=[]):
for track in tracklist:
log( '(ipodsync) Trying to remove: %s', track.title)
self.remove_from_ipod(track, [self.pl_podcasts])
def clean_playlist( self):
if not ipod_supported():
return False
for track in gpod.sw_get_playlist_tracks( self.pl_podcasts):
log( '(ipodsync) Trying to remove: %s', track.title)
self.remove_from_ipod( track, [ self.pl_podcasts ])
return gpod.sw_get_playlist_tracks(self.pl_podcasts)
def set_podcast_flags( self, track, episode):
if not ipod_supported():

View File

@ -339,9 +339,6 @@ class podcastChannel(ListType):
global_lock.release()
return not already_in_list
def is_played(self, item):
return libgpodder.gPodderLib().history_is_played( item.url)
def get_all_episodes( self):
episodes = []
@ -380,7 +377,7 @@ class podcastChannel(ListType):
newer += 1
if episode.is_downloaded():
downloaded += 1
if not self.is_played(episode):
if not episode.is_played():
unplayed += 1
return (available, downloaded, newer, unplayed)
@ -555,6 +552,32 @@ class podcastItem(object):
self.channel = channel
self.pubDate = ''
def is_played(self):
gl = libgpodder.gPodderLib()
return gl.history_is_played(self.url)
def age_in_days(self):
dt = util.file_modification_datetime(self.local_filename())
if dt is None:
return 0
else:
return (datetime.now()-dt).days
def is_old(self):
gl = libgpodder.gPodderLib()
return self.age_in_days() > gl.config.episode_old_age
def get_age_string(self):
age = self.age_in_days()
if age == 1:
return _('one day ago')
elif age > 1:
return _('%d days ago') % age
else:
return ''
age_prop = property(fget=get_age_string)
def one_line_description( self):
lines = self.description.strip().splitlines()
if not lines or lines[0] == '':
@ -665,7 +688,7 @@ class podcastItem(object):
channel_prop = property(fget=get_channel_title)
def get_played_string( self):
if not self.channel.is_played( self):
if not self.is_played():
return _('Unplayed')
return ''

View File

@ -37,12 +37,14 @@ import gtk
import os
import os.path
import glob
import stat
import re
import htmlentitydefs
import time
import locale
import gzip
import datetime
import urlparse
import urllib
@ -154,6 +156,24 @@ def calculate_size( path):
return 0L
def file_modification_datetime(filename):
"""
Returns the modification date of the specified file
as a datetime.datetime object or None if the modification
date cannot be determined.
"""
if not os.access(filename, os.R_OK):
return None
try:
s = os.stat(filename)
timestamp = s[stat.ST_MTIME]
return datetime.datetime.fromtimestamp(timestamp)
except:
log('Cannot get modification timestamp for %s', filename)
return None
def get_free_disk_space(path):
"""
Calculates the free disk space available to the current user