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:
parent
2db760e08c
commit
8ba93845be
26
ChangeLog
26
ChangeLog
|
@ -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
|
||||
|
||||
|
|
|
@ -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 ),
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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 ''
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue