Various fixes to download file/folder handling
This commit is contained in:
parent
2ceba6d99f
commit
714251a7a9
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue