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:
parent
d5aba0355e
commit
905014fc1b
18
ChangeLog
18
ChangeLog
|
@ -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)
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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> -> <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()
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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!)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue