Fri, 13 Jun 2008 09:37:45 +0200 <thp@perli.net>

Merge patch from Nick to add support for deleting played files on sync

	* data/gpodder.glade: Add check button for "Delete episodes on device
	that have been marked played in gPodder"
	* src/gpodder/config.py: Add two new config options:
	"mp3_player_delete_played" and "mp3_player_max_filename_length"
	* src/gpodder/gui.py: 
	* src/gpodder/libpodcasts.py: Add "is_deleted()" convenience function
	for a podcast episode
	* src/gpodder/sync.py: Clean-up and simplify parts of the code to make
	it better-structured and avoid code duplication; use the configuration
	variable for the MAX_FILENAME_LENGTH instead of hard-coding it to 100
	* src/gpodder/util.py: Split sanitize_filename() into two functions,
	detect_os_encoding() and sanitize_filename() for better code re-use
	(Closes: http://bugs.gpodder.org/show_bug.cgi?id=56; code written by
	Nick (nikosapi) and initial idea by Shane Donohoe, see the bug page)



git-svn-id: svn://svn.berlios.de/gpodder/trunk@734 b0d088ad-0a06-0410-aad2-9ed5178a7e87
This commit is contained in:
Thomas Perl 2008-06-13 07:41:36 +00:00
parent d5aba0355e
commit 905014fc1b
7 changed files with 129 additions and 63 deletions

View File

@ -1,3 +1,21 @@
Fri, 13 Jun 2008 09:37:45 +0200 <thp@perli.net>
Merge patch from Nick to add support for deleting played files on sync
* data/gpodder.glade: Add check button for "Delete episodes on device
that have been marked played in gPodder"
* src/gpodder/config.py: Add two new config options:
"mp3_player_delete_played" and "mp3_player_max_filename_length"
* src/gpodder/gui.py:
* src/gpodder/libpodcasts.py: Add "is_deleted()" convenience function
for a podcast episode
* src/gpodder/sync.py: Clean-up and simplify parts of the code to make
it better-structured and avoid code duplication; use the configuration
variable for the MAX_FILENAME_LENGTH instead of hard-coding it to 100
* src/gpodder/util.py: Split sanitize_filename() into two functions,
detect_os_encoding() and sanitize_filename() for better code re-use
(Closes: http://bugs.gpodder.org/show_bug.cgi?id=56; code written by
Nick (nikosapi) and initial idea by Shane Donohoe, see the bug page)
Sun, 08 Jun 2008 20:08:58 +0200 <thp@perli.net>
Patch from Jérôme Chabod to fix "minimize on start" bug (#123)

View File

@ -3501,7 +3501,7 @@
<widget class="GtkTable" id="table5">
<property name="border_width">10</property>
<property name="visible">True</property>
<property name="n_rows">12</property>
<property name="n_rows">13</property>
<property name="n_columns">4</property>
<property name="homogeneous">False</property>
<property name="row_spacing">5</property>
@ -3867,8 +3867,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
@ -3890,8 +3890,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
@ -3912,8 +3912,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">2</property>
<property name="right_attach">3</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="y_options"></property>
</packing>
</child>
@ -3941,8 +3941,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">3</property>
<property name="right_attach">4</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
@ -3959,6 +3959,7 @@ Filesystem-based MP3 player</property>
<property name="active">False</property>
<property name="inconsistent">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_only_sync_not_played_toggled" last_modification_time="Thu, 22 May 2008 15:17:47 GMT"/>
</widget>
<packing>
<property name="left_attach">1</property>
@ -4094,8 +4095,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="y_padding">2</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
@ -4117,8 +4118,8 @@ Filesystem-based MP3 player</property>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
@ -4144,6 +4145,30 @@ Filesystem-based MP3 player</property>
<property name="y_options">fill</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="delete_episodes_marked_played">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Delete episodes on device that have been marked played in gPodder</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>
<signal name="toggled" handler="on_delete_episodes_marked_played_toggled" last_modification_time="Thu, 22 May 2008 17:18:39 GMT"/>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">4</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>

View File

@ -76,6 +76,7 @@ gPodderSettings = {
'episode_list_descriptions': (bool, True),
'show_toolbar': (bool, True),
'ipod_write_gtkpod_extended': (bool, False),
'mp3_player_delete_played': (bool, False),
# Tray icon and notification settings
'display_tray_icon': (bool, False),
@ -106,6 +107,7 @@ gPodderSettings = {
'create_m3u_playlists': (bool, False),
'max_episodes_per_feed': (int, 200),
'mp3_player_use_scrobbler_log': (bool, False),
'mp3_player_max_filename_length': (int, 100),
'show_podcast_url_entry': (bool, True),
'maemo_allow_custom_player': (bool, False),
'rockbox_copy_coverart' : (bool, False),

View File

@ -1440,6 +1440,34 @@ class gPodder(GladeWidget):
episodes.append(episode)
self.new_episodes_show(episodes)
def get_all_episodes(self, exclude_nonsignificant=True ):
"""'exclude_nonsignificant' will exclude non-downloaded episodes
and all episodes from channels that are set to skip when syncing"""
episode_list = []
for channel in self.channels:
if not channel.sync_to_devices and exclude_nonsignificant:
log('Skipping channel: %s', channel.title, sender=self)
continue
for episode in channel.get_all_episodes():
if episode.is_downloaded() or not exclude_nonsignificant:
episode_list.append(episode)
return episode_list
def ipod_delete_played(self, device):
all_episodes = self.get_all_episodes( exclude_nonsignificant=False )
episodes_on_device = device.get_all_tracks()
for local_episode in all_episodes:
if local_episode.is_played() and not local_episode.is_locked() or local_episode.is_deleted():
if gl.config.device_type == 'filesystem':
local_episode_name = util.sanitize_filename(local_episode.sync_filename(), gl.config.mp3_player_max_filename_length)
else:
local_episode_name = local_episode.sync_filename()
for device_episode in episodes_on_device:
if device_episode.title == local_episode_name:
log("mp3_player_delete_played: removing %s" % device_episode.title)
device.remove_track(device_episode)
break
def on_sync_to_ipod_activate(self, widget, episodes=None):
Thread(target=self.sync_to_ipod_thread, args=(widget, episodes)).start()
@ -1463,16 +1491,12 @@ class gPodder(GladeWidget):
self.tray_icon.set_synchronisation_device(device)
if episodes is None:
episodes_to_sync = []
for channel in self.channels:
if not channel.sync_to_devices:
log('Skipping channel: %s', channel.title, sender=self)
continue
for episode in channel.get_all_episodes():
if episode.is_downloaded():
episodes_to_sync.append(episode)
episodes_to_sync = self.get_all_episodes()
device.add_tracks(episodes_to_sync)
# 'only_sync_not_played' must be used or else all the played
# tracks will be copied then immediately deleted
if gl.config.mp3_player_delete_played and gl.config.only_sync_not_played:
self.ipod_delete_played(device)
else:
device.add_tracks(episodes, force_played=True)
@ -2232,6 +2256,7 @@ class gPodderProperties(GladeWidget):
gl.config.connect_gtk_togglebutton('bluetooth_use_converter', self.bluetooth_use_converter)
gl.config.connect_gtk_filechooser( 'bluetooth_converter', self.bluetooth_converter, is_for_files=True)
gl.config.connect_gtk_togglebutton('ipod_write_gtkpod_extended', self.ipod_write_gtkpod_extended)
gl.config.connect_gtk_togglebutton('mp3_player_delete_played', self.delete_episodes_marked_played)
self.enable_notifications.set_sensitive(self.display_tray_icon.get_active())
self.minimize_to_tray.set_sensitive(self.display_tray_icon.get_active())
@ -2246,6 +2271,9 @@ class gPodderProperties(GladeWidget):
self.bluetooth_device_name.set_markup('<b>%s</b>'%gl.config.bluetooth_device_name)
self.chooserDownloadTo.set_current_folder(gl.downloaddir)
self.on_sync_delete.set_sensitive(not self.delete_episodes_marked_played.get_active())
self.on_sync_mark_played.set_sensitive(not self.delete_episodes_marked_played.get_active())
if tagging_supported():
gl.config.connect_gtk_togglebutton( 'update_tags', self.updatetags)
else:
@ -2370,6 +2398,17 @@ class gPodderProperties(GladeWidget):
def on_cbCustomSyncName_toggled( self, widget, *args):
self.entryCustomSyncName.set_sensitive( widget.get_active())
def on_only_sync_not_played_toggled( self, widget, *args):
self.delete_episodes_marked_played.set_sensitive( widget.get_active())
if not widget.get_active():
self.delete_episodes_marked_played.set_active(False)
def on_delete_episodes_marked_played_toggled( self, widget, *args):
if widget.get_active() and self.only_sync_not_played.get_active():
self.on_sync_leave.set_active(True)
self.on_sync_delete.set_sensitive(not widget.get_active())
self.on_sync_mark_played.set_sensitive(not widget.get_active())
def on_btnCustomSyncNameHelp_clicked( self, widget):
examples = [
'<i>{episode.title}</i> -&gt; <b>Interview with RMS</b>',
@ -2434,7 +2473,7 @@ class gPodderProperties(GladeWidget):
sync_widgets = ( self.only_sync_not_played, self.labelSyncOptions,
self.imageSyncOptions, self. separatorSyncOptions,
self.on_sync_mark_played, self.on_sync_delete,
self.on_sync_leave, self.label_after_sync, )
self.on_sync_leave, self.label_after_sync, self.delete_episodes_marked_played)
for widget in sync_widgets:
if active_item == 0:
widget.hide_all()

View File

@ -653,6 +653,9 @@ class podcastItem(object):
def is_played(self):
return gl.history_is_played(self.url)
def is_deleted(self):
return gl.history_is_downloaded(self.url) and not self.is_downloaded()
def age_in_days(self):
return util.file_age_in_days(self.local_filename())

View File

@ -427,24 +427,11 @@ class MP3PlayerDevice(Device):
# This is the maximum length of a file name that is
# created on the MP3 player, because FAT32 has a
# 255-character limit for the whole path
MAX_FILENAME_LENGTH = 100
MAX_FILENAME_LENGTH = gl.config.mp3_player_max_filename_length
def __init__(self):
Device.__init__(self)
# Try to detect OS encoding (by Leonid Ponomarev)
if 'LANG' in os.environ and '.' in os.environ['LANG']:
lang = os.environ['LANG']
(language, encoding) = lang.rsplit('.', 1)
log('Detected encoding: %s', encoding, sender=self)
self.enc = encoding
else:
# Using iso-8859-15 here as (hopefully) sane default
# see http://en.wikipedia.org/wiki/ISO/IEC_8859-1
log('Using ISO-8859-15 as encoding. If this', sender=self)
log('is incorrect, please set your $LANG variable.', sender=self)
self.enc = 'iso-8859-15'
self.enc = util.detect_os_encoding()
self.destination = gl.config.mp3_player_folder
self.buffer_size = 1024*1024 # 1 MiB
self.scrobbler_log = []
@ -472,31 +459,17 @@ class MP3PlayerDevice(Device):
if gl.config.fssync_channel_subfolders:
# Add channel title as subfolder
folder = episode.channel.title
# Don't allow extremely long folder names (filesystem limitations)
if len(folder) > self.MAX_FILENAME_LENGTH:
log('Limiting folder "%s" to %d characters.', folder, self.MAX_FILENAME_LENGTH, sender=self)
folder = folder[:self.MAX_FILENAME_LENGTH]
folder = re.sub('[/|?*<>:+\[\]\"\\\]', '_', folder.encode(self.enc, 'ignore'))
# Clean up the folder name for use on limited devices
folder = util.sanitize_filename(folder, self.MAX_FILENAME_LENGTH)
folder = os.path.join(self.destination, folder)
else:
folder = self.destination
from_file = episode.local_filename()
filename_base = episode.sync_filename()
# don't allow extremely long file names (filesystem limitations)
if len(filename_base) > self.MAX_FILENAME_LENGTH:
log('Limiting filename "%s" to %d characters.', filename_base, self.MAX_FILENAME_LENGTH, sender=self)
filename_base = filename_base[:self.MAX_FILENAME_LENGTH]
filename_base = util.sanitize_filename(episode.sync_filename(), self.MAX_FILENAME_LENGTH)
to_file = filename_base + os.path.splitext(from_file)[1].lower()
# Encode the file name to our system's
# encoding and remove the characters that are invalid
# for FAT-based drives (replace with the empty string)
to_file = re.sub('[/|?*<>:+\[\]\"\\\]', '_', to_file.encode(self.enc, 'ignore'))
# dirty workaround: on bad (empty) episode titles,
# we simply use the from_file basename
# (please, podcast authors, FIX YOUR RSS FEEDS!)

View File

@ -902,13 +902,7 @@ def open_website(url):
"""
threading.Thread(target=webbrowser.open, args=(url,)).start()
def sanitize_filename(filename):
"""
Generate a sanitized version of a filename that can
be written on disk (i.e. remove/replace invalid
characters and encode in the native language)
"""
def detect_os_encoding():
# Try to detect OS encoding (by Leonid Ponomarev)
if 'LANG' in os.environ and '.' in os.environ['LANG']:
lang = os.environ['LANG']
@ -921,8 +915,20 @@ def sanitize_filename(filename):
log('Using ISO-8859-15 as encoding. If this')
log('is incorrect, please set your $LANG variable.')
enc = 'iso-8859-15'
return enc
return re.sub('[/|?*<>:+\[\]\"\\\]', '_', filename.strip().encode(enc, 'ignore'))
def sanitize_filename(filename, max_length=0):
"""
Generate a sanitized version of a filename that can
be written on disk (i.e. remove/replace invalid
characters and encode in the native language) and
trim filename if greater than max_length (0 = no limit).
"""
if not max_length and len(filename) > max_length:
log('Limiting file/folder name "%s" to %d characters.', filename, max_length, sender=self)
filename = filename[:max_length]
return re.sub('[/|?*<>:+\[\]\"\\\]', '_', filename.strip().encode(detect_os_encoding(), 'ignore'))
def find_mount_point(directory):