Various fixes to download file/folder handling

This commit is contained in:
Thomas Perl 2011-08-07 23:39:46 +02:00
parent 2ceba6d99f
commit 714251a7a9
7 changed files with 80 additions and 83 deletions

View File

@ -60,7 +60,7 @@ class Podcast(object):
Sets a new title for this podcast that will be available
as the "title" attribute of this object."""
self._podcast.set_custom_title(title)
self._podcast.rename(title)
self.title = self._podcast.title
self._podcast.save()
@ -229,7 +229,7 @@ class PodcastClient(object):
mimetype_prefs=self._config.mimetype_prefs)
if podcast is not None:
if title is not None:
podcast.set_custom_title(title)
podcast.rename(title)
podcast.save()
return Podcast(podcast, self)

View File

@ -772,6 +772,10 @@ class DownloadTask(object):
new_mimetype)
self.__episode.set_mimetype(new_mimetype, commit=True)
# Re-evaluate filename and tempname to take care of podcast renames
# while downloads are running (which will change both file names)
self.filename = self.__episode.local_filename(create=False)
self.tempname = self.filename + '.partial'
shutil.move(self.tempname, self.filename)
# Model- and database-related updates after a download has finished

View File

@ -108,7 +108,7 @@ class gPodderChannel(BuilderWidget):
def on_btnOK_clicked(self, widget, *args):
self.channel.pause_subscription = self.cbSkipFeedUpdate.get_active()
self.channel.set_custom_title(self.entryTitle.get_text())
self.channel.rename(self.entryTitle.get_text())
self.channel.auth_username = self.FeedUsername.get_text().strip()
self.channel.auth_password = self.FeedPassword.get_text()

View File

@ -42,7 +42,7 @@ import rfc822
import hashlib
import feedparser
import collections
import weakref
import string
_ = gpodder.gettext
@ -425,18 +425,13 @@ class PodcastEpisode(PodcastModelObject):
return url
def find_unique_file_name(self, filename, extension):
current_try = util.sanitize_filename(filename, self.MAX_FILENAME_LENGTH)+extension
next_try_id = 2
# Remove leading and trailing whitespace + dots (to avoid hidden files)
filename = filename.strip('.' + string.whitespace) + extension
if self.download_filename == current_try and current_try is not None:
# We already have this filename - good!
return current_try
while self.db.episode_filename_exists(self.podcast_id, current_try):
current_try = '%s (%d)%s' % (filename, next_try_id, extension)
next_try_id += 1
return current_try
for name in util.generate_names(filename):
if (not self.db.episode_filename_exists(self.podcast_id, name) or
self.download_filename == name):
return name
def local_filename(self, create, force_update=False, check_only=False,
template=None, return_wanted_filename=False):
@ -674,7 +669,7 @@ class PodcastChannel(PodcastModelObject):
UNICODE_TRANSLATE = {ord(u'ö'): u'o', ord(u'ä'): u'a', ord(u'ü'): u'u'}
MAX_FOLDERNAME_LENGTH = 150
MAX_FOLDERNAME_LENGTH = 60
SECONDS_PER_WEEK = 7*24*60*60
EpisodeClass = PodcastEpisode
@ -824,9 +819,20 @@ class PodcastChannel(PodcastModelObject):
# updating the feed and adding saving episodes
tmp.save()
tmp.update(max_episodes, mimetype_prefs)
try:
tmp.update(max_episodes, mimetype_prefs)
except Exception, e:
logger.debug('Fetch failed. Removing buggy feed.')
tmp.remove_downloaded()
tmp.delete()
raise
# Determine the section in which this podcast should appear
tmp.section = tmp._get_content_type()
# Determine a new download folder now that we have the title
tmp.get_save_dir(force_new=True)
# Mark episodes as downloaded if files already exist (bug 902)
tmp.import_external_files()
@ -844,7 +850,8 @@ class PodcastChannel(PodcastModelObject):
return self.EpisodeClass.create_from_dict(d, self)
def _consume_custom_feed(self, custom_feed, max_episodes=0):
self.title = custom_feed.get_title()
if not self.title:
self.title = custom_feed.get_title()
self.link = custom_feed.get_link()
self.description = custom_feed.get_description()
self.cover_url = custom_feed.get_image()
@ -868,7 +875,8 @@ class PodcastChannel(PodcastModelObject):
#self.parse_error = feed.get('bozo_exception', None)
# Replace multi-space and newlines with single space (Maemo bug 11173)
self.title = re.sub('\s+', ' ', feed.feed.get('title', self.url))
if not self.title:
self.title = re.sub('\s+', ' ', feed.feed.get('title', self.url))
self.link = feed.feed.get('link', self.link)
self.description = feed.feed.get('subtitle', self.description)
@ -1100,19 +1108,13 @@ class PodcastChannel(PodcastModelObject):
image = property(_get_cover_url)
def set_custom_title( self, custom_title):
custom_title = custom_title.strip()
# if the custom title is the same as we have
if custom_title == self.title:
def rename(self, new_title):
new_title = new_title.strip()
if self.title == new_title:
return
# make sure self.download_folder is initialized
self.get_save_dir()
# rename folder if custom_title looks sane
new_folder_name = self.find_unique_folder_name(custom_title)
if len(new_folder_name) > 0 and new_folder_name != self.download_folder:
new_folder_name = self.find_unique_folder_name(new_title)
if new_folder_name and new_folder_name != self.download_folder:
new_folder = os.path.join(gpodder.downloads, new_folder_name)
old_folder = os.path.join(gpodder.downloads, self.download_folder)
if os.path.exists(old_folder):
@ -1129,9 +1131,9 @@ class PodcastChannel(PodcastModelObject):
logger.info('Removing %s', old_folder)
shutil.rmtree(old_folder, ignore_errors=True)
self.download_folder = new_folder_name
self.save()
self.title = custom_title
self.title = new_title
self.save()
def get_downloaded_episodes(self):
return filter(lambda e: e.was_downloaded(), self.get_all_episodes())
@ -1143,66 +1145,36 @@ class PodcastChannel(PodcastModelObject):
def find_unique_folder_name(self, download_folder):
# Remove trailing dots to avoid errors on Windows (bug 600)
download_folder = download_folder.strip().rstrip('.')
# Also remove leading dots to avoid hidden folders on Linux
download_folder = download_folder.strip('.' + string.whitespace)
current_try = util.sanitize_filename(download_folder, \
self.MAX_FOLDERNAME_LENGTH)
next_try_id = 2
for folder_name in util.generate_names(download_folder):
if (not self.db.podcast_download_folder_exists(folder_name) or
self.download_folder == folder_name):
return folder_name
while True:
if self.db.podcast_download_folder_exists(current_try):
current_try = '%s (%d)' % (download_folder, next_try_id)
next_try_id += 1
else:
return current_try
def get_save_dir(self):
urldigest = hashlib.md5(self.url).hexdigest()
sanitizedurl = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)
if self.download_folder is None:
def get_save_dir(self, force_new=False):
if self.download_folder is None or force_new:
# we must change the folder name, because it has not been set manually
fn_template = util.sanitize_filename(self.title, self.MAX_FOLDERNAME_LENGTH)
# if this is an empty string, try the basename
if len(fn_template) == 0:
logger.warn('That is one ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url)
fn_template = util.sanitize_filename(os.path.basename(self.url), self.MAX_FOLDERNAME_LENGTH)
# If the basename is also empty, use the first 6 md5 hexdigest chars of the URL
if len(fn_template) == 0:
logger.warn('That is one REALLY ugly feed you have here! (Report this to bugs.gpodder.org: %s)', self.url)
fn_template = urldigest # no need for sanitize_filename here
if not fn_template:
fn_template = util.sanitize_filename(self.url, self.MAX_FOLDERNAME_LENGTH)
# Find a unique folder name for this podcast
wanted_download_folder = self.find_unique_folder_name(fn_template)
download_folder = self.find_unique_folder_name(fn_template)
# if the download_folder has not been set, check if the (old) md5 filename exists
# TODO: Remove this code for "tres" release (pre-0.15.0 not supported after migration)
if self.download_folder is None and os.path.exists(os.path.join(gpodder.downloads, urldigest)):
self.download_folder = urldigest
# Try removing the download folder if it has been created previously
if self.download_folder is not None:
folder = os.path.join(gpodder.downloads, self.download_folder)
try:
os.rmdir(folder)
except OSError:
logger.info('Old download folder is kept for %s', self.url)
# we have a valid, new folder name in "current_try" -> use that!
if self.download_folder is not None and wanted_download_folder != self.download_folder:
# there might be an old download folder crawling around - move it!
new_folder_name = os.path.join(gpodder.downloads, wanted_download_folder)
old_folder_name = os.path.join(gpodder.downloads, self.download_folder)
if os.path.exists(old_folder_name):
if not os.path.exists(new_folder_name):
# Old folder exists, new folder does not -> simply rename
logger.info('Renaming %s => %s', old_folder_name,
new_folder_name)
os.rename(old_folder_name, new_folder_name)
else:
# Both folders exist -> move files and delete old folder
logger.info('Moving files from %s to %s',
old_folder_name, new_folder_name)
for file in glob.glob(os.path.join(old_folder_name, '*')):
shutil.move(file, new_folder_name)
logger.info('Removing %s', old_folder_name)
shutil.rmtree(old_folder_name, ignore_errors=True)
logger.info('Updating download_folder of %s to %s', self.url,
wanted_download_folder)
self.download_folder = wanted_download_folder
download_folder)
self.download_folder = download_folder
self.save()
save_dir = os.path.join(gpodder.downloads, self.download_folder)

View File

@ -102,6 +102,8 @@ class Controller(QObject):
else:
menu.append(helper.Action(_('Update'), 'update', podcast))
menu.append(helper.Action(_('Mark episodes as old'), 'mark-as-read', podcast))
menu.append(helper.Action('', '', None))
menu.append(helper.Action(_('Rename'), 'rename-podcast', podcast))
menu.append(helper.Action(_('Change section'), 'change-section', podcast))
menu.append(helper.Action('', '', None))
menu.append(helper.Action(_('Unsubscribe'), 'unsubscribe', podcast))
@ -167,6 +169,15 @@ class Controller(QObject):
self.root.resort_podcast_list()
self.start_input_dialog(section_changer(action.target))
elif action.action == 'rename-podcast':
def title_changer(podcast):
title = yield (_('New name:'), podcast.title,
_('Rename'))
if title and title != podcast.title:
podcast.rename(title)
self.root.resort_podcast_list()
self.start_input_dialog(title_changer(action.target))
def confirm_action(self, message, affirmative, callback):
def confirm(message, affirmative, callback):

View File

@ -163,7 +163,7 @@ class QEpisode(QObject):
self.source_url_changed.emit()
# Make sure the single channel is updated (main view)
self._podcast.qupdate()
self._podcast.changed.emit()
# Make sure that "All episodes", etc.. are updated
if finished_callback is not None:

View File

@ -57,6 +57,7 @@ import urllib2
import httplib
import webbrowser
import mimetypes
import itertools
import feedparser
@ -1341,3 +1342,12 @@ def write_m3u_playlist(m3u_filename, episodes, extm3u=True):
f.close()
def generate_names(filename):
basename, ext = os.path.splitext(filename)
for i in itertools.count():
if i:
yield '%s (%d)%s' % (basename, i+1, ext)
else:
yield filename