Code:
import os, numbers
import copy
from qt.core import (QApplication, Qt, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout,
QGroupBox, QComboBox, QRadioButton, QCheckBox, QToolButton, QLineEdit)
from calibre import prints
from calibre.constants import iswindows, isosx, islinux, DEBUG
from calibre.gui2 import error_dialog, Dispatcher, choose_dir
from calibre.gui2.actions.save_to_disk import SaveToDiskAction
from calibre.utils.formatter_functions import formatter_functions
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
from polyglot.builtins import itervalues
from calibre_plugins.action_chains.actions.base import ChainAction
from calibre_plugins.action_chains.common_utils import DragDropComboBox, get_icon
from calibre_plugins.action_chains.templates.dialogs import TemplateBox
from calibre_plugins.action_chains.templates import check_template
class ModifiedSaveToDiskAction(SaveToDiskAction):
def __init__(self, gui):
self.gui = gui
def save_to_disk(self, single_dir=False, single_format=None,
rows=None, write_opf=None, save_cover=None, path=None, opts=None):
if rows is None:
rows = self.gui.current_view().selectionModel().selectedRows()
if not rows or len(rows) == 0:
return error_dialog(self.gui, _('Cannot save to disk'),
_('No books selected'), show=True)
if not path:
path = choose_dir(self.gui, 'save to disk dialog',
_('Choose destination folder'))
if not path:
return
dpath = os.path.abspath(path).replace('/', os.sep)+os.sep
lpath = self.gui.library_view.model().db.library_path.replace('/',
os.sep)+os.sep
if dpath.startswith(lpath):
return error_dialog(self.gui, _('Not allowed'),
_('You are trying to save files into the calibre '
'library. This can cause corruption of your '
'library. Save to disk is meant to export '
'files from your calibre library elsewhere.'), show=True)
if self.gui.current_view() is self.gui.library_view:
from calibre.gui2.save import Saver
from calibre.library.save_to_disk import config
if opts is None:
opts = config().parse()
#print('debug1: opts.template: {}, opts.send_template: {}, opts.send_timefmt: {}, opts.timefmt: {}, opts.update_metadata: {}'.format(opts.template, opts.send_template, opts.send_timefmt, opts.timefmt, opts.update_metadata))
if single_format is not None:
opts.formats = single_format
# Special case for Kindle annotation files
if single_format.lower() in ['mbp','pdr','tan']:
opts.to_lowercase = False
opts.save_cover = False
opts.write_opf = False
opts.template = opts.send_template
opts.single_dir = single_dir
if write_opf is not None:
opts.write_opf = write_opf
if save_cover is not None:
opts.save_cover = save_cover
book_ids = set(map(self.gui.library_view.model().id, rows))
Saver(book_ids, self.gui.current_db, opts, path, parent=self.gui, pool=self.gui.spare_pool())
else:
paths = self.gui.current_view().model().paths(rows)
self.gui.device_manager.save_books(
Dispatcher(self.books_saved), paths, path)
class ConfigWidget(QWidget):
def __init__(self, plugin_action):
QWidget.__init__(self)
self.plugin_action = plugin_action
self.gui = plugin_action.gui
self.db = self.gui.current_db
self._init_controls()
def _init_controls(self):
l = self.l = QVBoxLayout()
self.setLayout(l)
self.path_box = QGroupBox(_('&Choose path:'))
l.addWidget(self.path_box)
path_layout = QVBoxLayout()
self.path_box.setLayout(path_layout)
self.path_combo = DragDropComboBox(self, drop_mode='file')
path_layout.addWidget(self.path_combo)
hl1 = QHBoxLayout()
path_layout.addLayout(hl1)
hl1.addWidget(self.path_combo, 1)
self.choose_path_button = QToolButton(self)
self.choose_path_button.setToolTip(_('Choose path'))
self.choose_path_button.setIcon(get_icon('document_open.png'))
self.choose_path_button.clicked.connect(self._choose_path)
hl1.addWidget(self.choose_path_button)
formats_gb = QGroupBox(_('Formats: '), self)
l.addWidget(formats_gb)
formats_l = QVBoxLayout()
formats_gb.setLayout(formats_l)
self.all_fmts_opt = QRadioButton(_('All formats'))
self.all_fmts_opt.setChecked(True)
formats_l.addWidget(self.all_fmts_opt)
self.fmts_list_layout = QHBoxLayout()
self.fmts_list_opt = QRadioButton(_('Save only specified formats'))
self.fmts_list_edit = QLineEdit()
self.fmts_list_edit.setToolTip(_('Comma separated list of formsts you want to save'))
self.fmts_list_layout.addWidget(self.fmts_list_opt, 1)
self.fmts_list_layout.addWidget(self.fmts_list_edit)
formats_l.addLayout(self.fmts_list_layout)
template_gb = QGroupBox(_('Template'))
template_gb_l = QVBoxLayout()
template_gb.setLayout(template_gb_l)
self.default_template_opt = QRadioButton(_('Use template define in calibre preferences'))
template_gb_l.addWidget(self.default_template_opt)
self.default_template_opt.setChecked(True)
self.user_template_opt = QRadioButton(_('Define a custom template for this action'))
template_gb_l.addWidget(self.user_template_opt)
l.addWidget(template_gb)
self.user_template_opt.toggled.connect(self._on_template_opt_toggled)
self.template_edit = TemplateLineEditor(self)
template_gb_l.addWidget(self.template_edit)
opts_gb = QGroupBox(_('Options: '), self)
l.addWidget(opts_gb)
opts_l = QVBoxLayout()
opts_gb.setLayout(opts_l)
self.single_folder_chk = QCheckBox(_('Save in a single folder'))
opts_l.addWidget(self.single_folder_chk)
self.save_cover_chk = QCheckBox(_('Save cover'))
self.save_cover_chk.setChecked(True)
opts_l.addWidget(self.save_cover_chk)
self.save_opf_chk = QCheckBox(_('Save opf'))
self.save_opf_chk.setChecked(True)
opts_l.addWidget(self.save_opf_chk)
l.addStretch(1)
# self.all_formats = self.gui.library_view.model().db.all_formats()
self._on_template_opt_toggled()
self.setMinimumSize(400,500)
def _on_template_opt_toggled(self):
self.template_edit.setEnabled(self.user_template_opt.isChecked())
def _choose_path(self):
path = choose_dir(self.gui, 'save to disk dialog',
_('Choose destination folder'))
if not path:
return
if iswindows:
path = os.path.normpath(path)
self.block_events = True
existing_index = self.path_combo.findText(path, Qt.MatchExactly)
if existing_index >= 0:
self.path_combo.setCurrentIndex(existing_index)
else:
self.path_combo.insertItem(0, path)
self.path_combo.setCurrentIndex(0)
self.block_events = False
def load_settings(self, settings):
if settings:
if settings['fmt_opt'] == 'all_formats':
self.all_fmts_opt.setChecked(True)
elif settings['fmt_opt'] == 'selected_formats':
self.fmts_list_opt.setChecked(True)
fmt = settings['formats']
self.fmts_list_edit.setText(fmt)
self.path_combo.setCurrentText(settings['path'])
template_opt = settings.get('template_opt', 'default')
if template_opt == 'default':
self.default_template_opt.setChecked(True)
elif template_opt == 'user':
self.user_template_opt.setChecked(True)
self.template_edit.setText(settings['template'])
self.single_folder_chk.setChecked(settings['single_folder'])
self.save_cover_chk.setChecked(settings['save_cover'])
self.save_opf_chk.setChecked(settings['save_opf'])
def save_settings(self):
settings = {}
settings['path'] = self.path_combo.currentText().strip()
if self.fmts_list_opt.isChecked():
settings['fmt_opt'] = 'selected_formats'
settings['formats'] = self.fmts_list_edit.text()
elif self.all_fmts_opt.isChecked():
settings['fmt_opt'] = 'all_formats'
if self.default_template_opt.isChecked():
settings['template_opt'] = 'default'
elif self.user_template_opt.isChecked():
settings['template_opt'] = 'user'
settings['template'] = self.template_edit.text()
settings['single_folder'] = self.single_folder_chk.isChecked()
settings['save_cover'] = self.save_cover_chk.isChecked()
settings['save_opf'] = self.save_opf_chk.isChecked()
return settings
class SaveToAction(ChainAction):
name = 'Save to disk'
def run(self, gui, settings, chain):
from calibre.library.save_to_disk import config
opts = copy.deepcopy(config().parse())
path = settings['path']
single_dir = settings['single_folder']
if settings['fmt_opt'] == 'selected_formats':
opts.formats = settings['formats'].lower()
if settings.get('template_opt', 'default') == 'user':
opts.template = settings['template']
save_cover = settings['save_cover']
write_opf = settings['save_opf']
action = ModifiedSaveToDiskAction(gui)
action.save_to_disk(single_dir=single_dir, write_opf=write_opf,
save_cover=save_cover, path=path, opts=opts)
def validate(self, settings):
if not settings:
return (_('Settings Error'), _('You must configure this action before running it'))
path = settings['path']
if not path:
return (_('No Directory'), _('You must specify a path to valid path'))
if not (os.access(path, os.W_OK) and os.path.isdir(path)):
return (_('Invalid direcotry'), _('Path is not a writable directory: {}'.format(path)))
if not settings.get('fmt_opt'):
return (_('No Path'), _('You must choose a format option'))
if settings['fmt_opt'] == 'selected_formats':
if not settings['formats']:
return (_('No format'), _('You must choose a valid format value'))
template_opt = settings.get('template_opt', 'default')
if template_opt == 'user':
template = settings['template']
if not template:
return (_('No template'), _('You must specify a template'))
else:
# only calibre template functions, exclude Action Chains defined functions
template_functions = formatter_functions().get_functions()
is_template_valid = check_template(template, self.plugin_action, print_error=False, template_functions=template_functions)
if is_template_valid is not True:
return is_template_valid
return True
def config_widget(self):
return ConfigWidget
Edit: This action has been modified in a way the is not backward compatible with previous iterations. If using the new code, you are advised to re-create the action from scratch.