fix #374 export to local folder filename customizable again

with a checkbox to export all remaining episodes to same folder.
This restores the ability to choose the filename for each episode,
while keeping export of a large number of episodes convenient (click export remaining...)
This commit is contained in:
Eric Le Lay 2018-10-13 18:18:25 +02:00
parent fe0b5c2052
commit cdbeb04eb5
4 changed files with 209 additions and 19 deletions

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.0"/>
<object class="GtkFileChooserDialog" id="gPodderExportToLocalFolder">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Select destination</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<property name="action">save</property>
<property name="do_overwrite_confirmation">True</property>
<property name="preview_widget_active">False</property>
<property name="use_preview_label">False</property>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btnOK">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="allsamefolder">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<!-- to be recognized by the embedded GtkFileChooser -->
<action-widgets>
<action-widget response="-3">btnOK</action-widget>
<action-widget response="-6">btnCancel</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -125,6 +125,11 @@ defaults = {
'height': 400,
'x': -1, 'y': -1, 'maximized': False,
},
'export_to_local_folder': {
'width': 500,
'height': 400,
'x': -1, 'y': -1, 'maximized': False,
}
},
'toolbar': False,

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
# gPodder - A media aggregator and podcast client
# Copyright (c) 2005-2018 The gPodder Team
#
# gPodder is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# gPodder is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os
from gi.repository import Gtk, Pango
import gpodder
from gpodder import util
from gpodder.gtkui.interface.common import BuilderWidget
_ = gpodder.gettext
N_ = gpodder.ngettext
class gPodderExportToLocalFolder(BuilderWidget):
""" Export to Local Folder UI: file dialog + checkbox to save all to same folder """
def new(self):
self._config.connect_gtk_window(self.gPodderExportToLocalFolder,
'export_to_local_folder', True)
self._ok = False
def on_btnOK_clicked(self, widget):
self._ok = True
self.gPodderExportToLocalFolder.hide()
def on_btnCancel_clicked(self, widget):
self.gPodderExportToLocalFolder.hide()
def save_as(self, initial_directory, filename, remaining=0):
"""
blocking method: prompt for save to local folder
:param str initial_directory: folder to show to user or None
:param str filename: default export filename
:param int remaining: remaining episodes (to show/hide and customize checkbox label)
:return (bool, str, str, bool): notCancelled, selected folder, selected path,
save all remaining episodes with default params
"""
if remaining:
self.allsamefolder.set_label(
N_('Export remaining %(count)d episode to this folder with its default name',
'Export remaining %(count)d episodes to this folder with their default name',
remaining) % {'count': remaining})
else:
self.allsamefolder.hide()
if initial_directory is None:
initial_directory = os.path.expanduser('~')
self.gPodderExportToLocalFolder.set_current_folder(initial_directory)
self.gPodderExportToLocalFolder.set_current_name(filename)
self._ok = False
self.gPodderExportToLocalFolder.run()
notCancelled = self._ok
allRemainingDefault = self.allsamefolder.get_active()
if notCancelled:
folder = self.gPodderExportToLocalFolder.get_current_folder()
filename = self.gPodderExportToLocalFolder.get_filename()
else:
folder = None
filename = None
return (notCancelled, folder, filename, allRemainingDefault)

View File

@ -48,6 +48,7 @@ from gpodder.gtkui import shownotes
from gpodder.gtkui.config import UIConfig
from gpodder.gtkui.desktop.channel import gPodderChannel
from gpodder.gtkui.desktop.episodeselector import gPodderEpisodeSelector
from gpodder.gtkui.desktop.exportlocal import gPodderExportToLocalFolder
from gpodder.gtkui.desktop.podcastdirectory import gPodderPodcastDirectory
from gpodder.gtkui.desktop.preferences import gPodderPreferences
from gpodder.gtkui.desktop.sync import gPodderSyncUI
@ -1787,30 +1788,56 @@ class gPodder(BuilderWidget, dbus.service.Object):
return filename
def save_episodes_as_file(self, episodes):
def do_save_episode(copy_from, copy_to):
try:
shutil.copyfile(copy_from, copy_to)
except (OSError, IOError) as e:
logger.warn('Error copying from %s to %s: %r', copy_from, copy_to, e, exc_info=True)
folder, filename = os.path.split(copy_to)
# Remove characters not supported by VFAT (#282)
new_filename = re.sub(r"[\"*/:<>?\\|]", "_", filename)
destination = os.path.join(folder, new_filename)
if (copy_to != destination):
shutil.copyfile(copy_from, destination)
else:
raise
PRIVATE_FOLDER_ATTRIBUTE = '_save_episodes_as_file_folder'
folder = getattr(self, PRIVATE_FOLDER_ATTRIBUTE, None)
(notCancelled, folder) = self.show_folder_select_dialog(initial_directory=folder)
setattr(self, PRIVATE_FOLDER_ATTRIBUTE, folder)
allRemainingDefault = False
remaining = len(episodes)
dialog = gPodderExportToLocalFolder(self.main_window,
_config=self.config)
for episode in episodes:
remaining -= 1
if episode.was_downloaded(and_exists=True):
copy_from = episode.local_filename(create=False)
assert copy_from is not None
if notCancelled:
for episode in episodes:
if episode.was_downloaded(and_exists=True):
copy_from = episode.local_filename(create=False)
assert copy_from is not None
base, extension = os.path.splitext(copy_from)
filename = self.build_filename(episode.sync_filename(), extension)
base, extension = os.path.splitext(copy_from)
filename = self.build_filename(episode.sync_filename(), extension)
copy_to = os.path.join(folder, filename)
try:
shutil.copyfile(copy_from, copy_to)
except (OSError, IOError) as e:
# Remove characters not supported by VFAT (#282)
new_filename = re.sub(r"[\"*/:<>?\\|]", "_", filename)
destination = os.path.join(folder, new_filename)
if (copy_to != destination):
shutil.copyfile(copy_from, destination)
try:
if allRemainingDefault:
do_save_episode(copy_from, os.path.join(folder, filename))
else:
(notCancelled, folder, dest_path, allRemainingDefault) = dialog.save_as(folder, filename, remaining)
if notCancelled:
do_save_episode(copy_from, dest_path)
else:
raise
break
except (OSError, IOError) as e:
if remaining:
msg = _('Error saving to local folder: %(error)r.\n'
'Would you like to continue?') % dict(error=e)
if not self.show_confirmation(msg, _('Error saving to local folder')):
logger.warn("Save to Local Folder cancelled following error")
break
else:
self.notification(_('Error saving to local folder: %(error)r') % dict(error=e),
_('Error saving to local folder'), important=True)
setattr(self, PRIVATE_FOLDER_ATTRIBUTE, folder)
def copy_episodes_bluetooth(self, episodes):
episodes_to_copy = [e for e in episodes if e.was_downloaded(and_exists=True)]