%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/dialogs/ |
| Current File : //lib/calibre/calibre/gui2/dialogs/plugin_updater.py |
#!/usr/bin/env python3
__license__ = 'GPL v3'
__copyright__ = '2011, Grant Drake <grant.drake@gmail.com>'
__docformat__ = 'restructuredtext en'
import datetime
import re
import traceback
from qt.core import (
QAbstractItemView, QAbstractTableModel, QAction, QApplication, QBrush, QComboBox,
QDialog, QDialogButtonBox, QFont, QFrame, QHBoxLayout, QIcon, QLabel, QLineEdit,
QModelIndex, QPixmap, QSize, QSortFilterProxyModel, Qt, QTableView, QUrl,
QVBoxLayout
)
from calibre import prints
from calibre.constants import (
DEBUG, __appname__, __version__, ismacos, iswindows, numeric_version
)
from calibre.customize import PluginInstallationType
from calibre.customize.ui import (
NameConflict, add_plugin, disable_plugin, enable_plugin, has_external_plugins,
initialized_plugins, is_disabled, remove_plugin
)
from calibre.gui2 import error_dialog, gprefs, info_dialog, open_url, question_dialog
from calibre.gui2.preferences.plugins import ConfigWidget
from calibre.utils.date import UNDEFINED_DATE, format_date
from calibre.utils.https import get_https_resource_securely
from polyglot.builtins import itervalues
SERVER = 'https://code.calibre-ebook.com/plugins/'
INDEX_URL = '%splugins.json.bz2' % SERVER
FILTER_ALL = 0
FILTER_INSTALLED = 1
FILTER_UPDATE_AVAILABLE = 2
FILTER_NOT_INSTALLED = 3
def get_plugin_updates_available(raise_error=False):
'''
API exposed to read whether there are updates available for any
of the installed user plugins.
Returns None if no updates found
Returns list(DisplayPlugin) of plugins installed that have a new version
'''
if not has_external_plugins():
return None
display_plugins = read_available_plugins(raise_error=raise_error)
if display_plugins:
update_plugins = list(filter(filter_upgradeable_plugins, display_plugins))
if len(update_plugins) > 0:
return update_plugins
return None
def filter_upgradeable_plugins(display_plugin):
return display_plugin.installation_type is PluginInstallationType.EXTERNAL \
and display_plugin.is_upgrade_available()
def filter_not_installed_plugins(display_plugin):
return not display_plugin.is_installed()
def read_available_plugins(raise_error=False):
import bz2
import json
display_plugins = []
try:
raw = get_https_resource_securely(INDEX_URL)
if not raw:
return
raw = json.loads(bz2.decompress(raw))
except:
if raise_error:
raise
traceback.print_exc()
return
for plugin in itervalues(raw):
try:
display_plugin = DisplayPlugin(plugin)
get_installed_plugin_status(display_plugin)
display_plugins.append(display_plugin)
except:
if DEBUG:
prints('======= Plugin Parse Error =======')
traceback.print_exc()
import pprint
pprint.pprint(plugin)
display_plugins = sorted(display_plugins, key=lambda k: k.name)
return display_plugins
def get_installed_plugin_status(display_plugin):
display_plugin.installed_version = None
display_plugin.plugin = None
for plugin in initialized_plugins():
if plugin.name == display_plugin.qname \
and plugin.installation_type is not PluginInstallationType.BUILTIN:
display_plugin.plugin = plugin
display_plugin.installed_version = plugin.version
break
if display_plugin.uninstall_plugins:
# Plugin requires a specific plugin name to be uninstalled first
# This could occur when a plugin is renamed (Kindle Collections)
# or multiple plugins deprecated into a newly named one.
# Check whether user has the previous version(s) installed
plugins_to_remove = list(display_plugin.uninstall_plugins)
for plugin_to_uninstall in plugins_to_remove:
found = False
for plugin in initialized_plugins():
if plugin.name == plugin_to_uninstall:
found = True
break
if not found:
display_plugin.uninstall_plugins.remove(plugin_to_uninstall)
class ImageTitleLayout(QHBoxLayout):
'''
A reusable layout widget displaying an image followed by a title
'''
def __init__(self, parent, icon_name, title):
QHBoxLayout.__init__(self)
title_font = QFont()
title_font.setPointSize(16)
title_image_label = QLabel(parent)
pixmap = QPixmap()
pixmap.load(I(icon_name))
if pixmap is None:
error_dialog(parent, _('Restart required'),
_('You must restart calibre before using this plugin!'), show=True)
else:
title_image_label.setPixmap(pixmap)
title_image_label.setMaximumSize(32, 32)
title_image_label.setScaledContents(True)
self.addWidget(title_image_label)
shelf_label = QLabel(title, parent)
shelf_label.setFont(title_font)
self.addWidget(shelf_label)
self.insertStretch(-1)
class SizePersistedDialog(QDialog):
'''
This dialog is a base class for any dialogs that want their size/position
restored when they are next opened.
'''
initial_extra_size = QSize(0, 0)
def __init__(self, parent, unique_pref_name):
QDialog.__init__(self, parent)
self.unique_pref_name = unique_pref_name
self.geom = gprefs.get(unique_pref_name, None)
self.finished.connect(self.dialog_closing)
def resize_dialog(self):
if self.geom is None:
self.resize(self.sizeHint()+self.initial_extra_size)
else:
QApplication.instance().safe_restore_geometry(self, self.geom)
def dialog_closing(self, result):
geom = bytearray(self.saveGeometry())
gprefs[self.unique_pref_name] = geom
class PluginFilterComboBox(QComboBox):
def __init__(self, parent):
QComboBox.__init__(self, parent)
items = [_('All'), _('Installed'), _('Update available'), _('Not installed')]
self.addItems(items)
class DisplayPlugin:
def __init__(self, plugin):
self.name = plugin['index_name']
self.qname = plugin.get('name', self.name)
self.forum_link = plugin['thread_url']
self.zip_url = SERVER + plugin['file']
self.installed_version = None
self.description = plugin['description']
self.donation_link = plugin['donate']
self.available_version = tuple(plugin['version'])
self.release_date = datetime.datetime(*tuple(map(int, re.split(r'\D', plugin['last_modified'])))[:6]).date()
self.calibre_required_version = tuple(plugin['minimum_calibre_version'])
self.author = plugin['author']
self.platforms = plugin['supported_platforms']
self.uninstall_plugins = plugin['uninstall'] or []
self.has_changelog = plugin['history']
self.is_deprecated = plugin['deprecated']
self.installation_type = PluginInstallationType.EXTERNAL
def is_disabled(self):
if self.plugin is None:
return False
return is_disabled(self.plugin)
def is_installed(self):
return self.installed_version is not None
def name_matches_filter(self, filter_text):
# filter_text is already lowercase @set_filter_text
return filter_text in icu_lower(self.name) # case-insensitive filtering
def is_upgrade_available(self):
if isinstance(self.installed_version, str):
return True
return self.is_installed() and (self.installed_version < self.available_version or self.is_deprecated)
def is_valid_platform(self):
if iswindows:
return 'windows' in self.platforms
if ismacos:
return 'osx' in self.platforms
return 'linux' in self.platforms
def is_valid_calibre(self):
return numeric_version >= self.calibre_required_version
def is_valid_to_install(self):
return self.is_valid_platform() and self.is_valid_calibre() and not self.is_deprecated
class DisplayPluginSortFilterModel(QSortFilterProxyModel):
def __init__(self, parent):
QSortFilterProxyModel.__init__(self, parent)
self.setSortRole(Qt.ItemDataRole.UserRole)
self.setSortCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
self.filter_criteria = FILTER_ALL
self.filter_text = ""
def filterAcceptsRow(self, sourceRow, sourceParent):
index = self.sourceModel().index(sourceRow, 0, sourceParent)
display_plugin = self.sourceModel().display_plugins[index.row()]
if self.filter_criteria == FILTER_ALL:
return not (display_plugin.is_deprecated and not display_plugin.is_installed()) and display_plugin.name_matches_filter(self.filter_text)
if self.filter_criteria == FILTER_INSTALLED:
return display_plugin.is_installed() and display_plugin.name_matches_filter(self.filter_text)
if self.filter_criteria == FILTER_UPDATE_AVAILABLE:
return display_plugin.is_upgrade_available() and display_plugin.name_matches_filter(self.filter_text)
if self.filter_criteria == FILTER_NOT_INSTALLED:
return not display_plugin.is_installed() and not display_plugin.is_deprecated and display_plugin.name_matches_filter(self.filter_text)
return False
def set_filter_criteria(self, filter_value):
self.filter_criteria = filter_value
self.invalidateFilter()
def set_filter_text(self, filter_text_value):
self.filter_text = icu_lower(str(filter_text_value))
self.invalidateFilter()
class DisplayPluginModel(QAbstractTableModel):
def __init__(self, display_plugins):
QAbstractTableModel.__init__(self)
self.display_plugins = display_plugins
self.headers = list(map(str, [_('Plugin name'), _('Donate'), _('Status'), _('Installed'),
_('Available'), _('Released'), _('calibre'), _('Author')]))
def rowCount(self, *args):
return len(self.display_plugins)
def columnCount(self, *args):
return len(self.headers)
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
return self.headers[section]
return None
def data(self, index, role):
if not index.isValid():
return None
row, col = index.row(), index.column()
if row < 0 or row >= self.rowCount():
return None
display_plugin = self.display_plugins[row]
if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.UserRole]:
if col == 0:
return display_plugin.name
if col == 1:
if display_plugin.donation_link:
return _('PayPal')
if col == 2:
return self._get_status(display_plugin)
if col == 3:
return self._get_display_version(display_plugin.installed_version)
if col == 4:
return self._get_display_version(display_plugin.available_version)
if col == 5:
if role == Qt.ItemDataRole.UserRole:
return self._get_display_release_date(display_plugin.release_date, 'yyyyMMdd')
else:
return self._get_display_release_date(display_plugin.release_date)
if col == 6:
return self._get_display_version(display_plugin.calibre_required_version)
if col == 7:
return display_plugin.author
elif role == Qt.ItemDataRole.DecorationRole:
if col == 0:
return self._get_status_icon(display_plugin)
if col == 1:
if display_plugin.donation_link:
return QIcon(I('donate.png'))
elif role == Qt.ItemDataRole.ToolTipRole:
if col == 1 and display_plugin.donation_link:
return _('This plugin is FREE but you can reward the developer for their effort\n'
'by donating to them via PayPal.\n\n'
'Right-click and choose Donate to reward: ')+display_plugin.author
else:
return self._get_status_tooltip(display_plugin)
elif role == Qt.ItemDataRole.ForegroundRole:
if col != 1: # Never change colour of the donation column
if display_plugin.is_deprecated:
return QBrush(Qt.GlobalColor.blue)
if display_plugin.is_disabled():
return QBrush(Qt.GlobalColor.gray)
return None
def plugin_to_index(self, display_plugin):
for i, p in enumerate(self.display_plugins):
if display_plugin == p:
return self.index(i, 0, QModelIndex())
return QModelIndex()
def refresh_plugin(self, display_plugin):
idx = self.plugin_to_index(display_plugin)
self.dataChanged.emit(idx, idx)
def _get_display_release_date(self, date_value, format='dd MMM yyyy'):
if date_value and date_value != UNDEFINED_DATE:
return format_date(date_value, format)
return None
def _get_display_version(self, version):
if version is None:
return ''
return '.'.join([str(v) for v in list(version)])
def _get_status(self, display_plugin):
if not display_plugin.is_valid_platform():
return _('Platform unavailable')
if not display_plugin.is_valid_calibre():
return _('calibre upgrade required')
if display_plugin.is_installed():
if display_plugin.is_deprecated:
return _('Plugin deprecated')
elif display_plugin.is_upgrade_available():
return _('New version available')
else:
return _('Latest version installed')
return _('Not installed')
def _get_status_icon(self, display_plugin):
if display_plugin.is_deprecated:
icon_name = 'plugin_deprecated.png'
elif display_plugin.is_disabled():
if display_plugin.is_upgrade_available():
if display_plugin.is_valid_to_install():
icon_name = 'plugin_disabled_valid.png'
else:
icon_name = 'plugin_disabled_invalid.png'
else:
icon_name = 'plugin_disabled_ok.png'
elif display_plugin.is_installed():
if display_plugin.is_upgrade_available():
if display_plugin.is_valid_to_install():
icon_name = 'plugin_upgrade_valid.png'
else:
icon_name = 'plugin_upgrade_invalid.png'
else:
icon_name = 'plugin_upgrade_ok.png'
else: # A plugin available not currently installed
if display_plugin.is_valid_to_install():
icon_name = 'plugin_new_valid.png'
else:
icon_name = 'plugin_new_invalid.png'
return QIcon(I('plugins/' + icon_name))
def _get_status_tooltip(self, display_plugin):
if display_plugin.is_deprecated:
return (_('This plugin has been deprecated and should be uninstalled')+'\n\n'+
_('Right-click to see more options'))
if not display_plugin.is_valid_platform():
return (_('This plugin can only be installed on: %s') %
', '.join(display_plugin.platforms)+'\n\n'+
_('Right-click to see more options'))
if numeric_version < display_plugin.calibre_required_version:
return (_('You must upgrade to at least calibre %s before installing this plugin') %
self._get_display_version(display_plugin.calibre_required_version)+'\n\n'+
_('Right-click to see more options'))
if display_plugin.installed_version is None:
return (_('You can install this plugin')+'\n\n'+
_('Right-click to see more options'))
try:
if display_plugin.installed_version < display_plugin.available_version:
return (_('A new version of this plugin is available')+'\n\n'+
_('Right-click to see more options'))
except Exception:
return (_('A new version of this plugin is available')+'\n\n'+
_('Right-click to see more options'))
return (_('This plugin is installed and up-to-date')+'\n\n'+
_('Right-click to see more options'))
class PluginUpdaterDialog(SizePersistedDialog):
initial_extra_size = QSize(350, 100)
forum_label_text = _('Plugin homepage')
def __init__(self, gui, initial_filter=FILTER_UPDATE_AVAILABLE):
SizePersistedDialog.__init__(self, gui, 'Plugin Updater plugin:plugin updater dialog')
self.gui = gui
self.forum_link = None
self.zip_url = None
self.model = None
self.do_restart = False
self._initialize_controls()
self._create_context_menu()
try:
display_plugins = read_available_plugins(raise_error=True)
except Exception:
display_plugins = []
import traceback
error_dialog(self.gui, _('Update Check Failed'),
_('Unable to reach the plugin index page.'),
det_msg=traceback.format_exc(), show=True)
if display_plugins:
self.model = DisplayPluginModel(display_plugins)
self.proxy_model = DisplayPluginSortFilterModel(self)
self.proxy_model.setSourceModel(self.model)
self.plugin_view.setModel(self.proxy_model)
self.plugin_view.resizeColumnsToContents()
self.plugin_view.selectionModel().currentRowChanged.connect(self._plugin_current_changed)
self.plugin_view.doubleClicked.connect(self.install_button.click)
self.filter_combo.setCurrentIndex(initial_filter)
self._select_and_focus_view()
else:
self.filter_combo.setEnabled(False)
# Cause our dialog size to be restored from prefs or created on first usage
self.resize_dialog()
def _initialize_controls(self):
self.setWindowTitle(_('User plugins'))
self.setWindowIcon(QIcon(I('plugins/plugin_updater.png')))
layout = QVBoxLayout(self)
self.setLayout(layout)
title_layout = ImageTitleLayout(self, 'plugins/plugin_updater.png',
_('User plugins'))
layout.addLayout(title_layout)
header_layout = QHBoxLayout()
layout.addLayout(header_layout)
self.filter_combo = PluginFilterComboBox(self)
self.filter_combo.setMinimumContentsLength(20)
self.filter_combo.currentIndexChanged[int].connect(self._filter_combo_changed)
la = QLabel(_('Filter list of &plugins')+':', self)
la.setBuddy(self.filter_combo)
header_layout.addWidget(la)
header_layout.addWidget(self.filter_combo)
header_layout.addStretch(10)
# filter plugins by name
la = QLabel(_('Filter by &name')+':', self)
header_layout.addWidget(la)
self.filter_by_name_lineedit = QLineEdit(self)
la.setBuddy(self.filter_by_name_lineedit)
self.filter_by_name_lineedit.setText("")
self.filter_by_name_lineedit.textChanged.connect(self._filter_name_lineedit_changed)
header_layout.addWidget(self.filter_by_name_lineedit)
self.plugin_view = QTableView(self)
self.plugin_view.horizontalHeader().setStretchLastSection(True)
self.plugin_view.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.plugin_view.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.plugin_view.setAlternatingRowColors(True)
self.plugin_view.setSortingEnabled(True)
self.plugin_view.setIconSize(QSize(28, 28))
layout.addWidget(self.plugin_view)
details_layout = QHBoxLayout()
layout.addLayout(details_layout)
forum_label = self.forum_label = QLabel('')
forum_label.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard)
forum_label.linkActivated.connect(self._forum_label_activated)
details_layout.addWidget(QLabel(_('Description')+':', self), 0, Qt.AlignmentFlag.AlignLeft)
details_layout.addWidget(forum_label, 1, Qt.AlignmentFlag.AlignRight)
self.description = QLabel(self)
self.description.setFrameStyle(QFrame.Shape.Panel | QFrame.Shadow.Sunken)
self.description.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
self.description.setMinimumHeight(40)
self.description.setWordWrap(True)
layout.addWidget(self.description)
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
self.button_box.rejected.connect(self.reject)
self.finished.connect(self._finished)
self.install_button = self.button_box.addButton(_('&Install'), QDialogButtonBox.ButtonRole.AcceptRole)
self.install_button.setToolTip(_('Install the selected plugin'))
self.install_button.clicked.connect(self._install_clicked)
self.install_button.setEnabled(False)
self.configure_button = self.button_box.addButton(' '+_('&Customize plugin ')+' ', QDialogButtonBox.ButtonRole.ResetRole)
self.configure_button.setToolTip(_('Customize the options for this plugin'))
self.configure_button.clicked.connect(self._configure_clicked)
self.configure_button.setEnabled(False)
layout.addWidget(self.button_box)
def update_forum_label(self):
txt = ''
if self.forum_link:
txt = f'<a href="{self.forum_link}">{self.forum_label_text}</a>'
self.forum_label.setText(txt)
def _create_context_menu(self):
self.plugin_view.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
self.install_action = QAction(QIcon(I('plugins/plugin_upgrade_ok.png')), _('&Install'), self)
self.install_action.setToolTip(_('Install the selected plugin'))
self.install_action.triggered.connect(self._install_clicked)
self.install_action.setEnabled(False)
self.plugin_view.addAction(self.install_action)
self.forum_action = QAction(QIcon(I('plugins/mobileread.png')), _('Plugin &forum thread'), self)
self.forum_action.triggered.connect(self._forum_label_activated)
self.forum_action.setEnabled(False)
self.plugin_view.addAction(self.forum_action)
sep1 = QAction(self)
sep1.setSeparator(True)
self.plugin_view.addAction(sep1)
self.toggle_enabled_action = QAction(_('Enable/&disable plugin'), self)
self.toggle_enabled_action.setToolTip(_('Enable or disable this plugin'))
self.toggle_enabled_action.triggered.connect(self._toggle_enabled_clicked)
self.toggle_enabled_action.setEnabled(False)
self.plugin_view.addAction(self.toggle_enabled_action)
self.uninstall_action = QAction(_('&Remove plugin'), self)
self.uninstall_action.setToolTip(_('Uninstall the selected plugin'))
self.uninstall_action.triggered.connect(self._uninstall_clicked)
self.uninstall_action.setEnabled(False)
self.plugin_view.addAction(self.uninstall_action)
sep2 = QAction(self)
sep2.setSeparator(True)
self.plugin_view.addAction(sep2)
self.donate_enabled_action = QAction(QIcon(I('donate.png')), _('Donate to developer'), self)
self.donate_enabled_action.setToolTip(_('Donate to the developer of this plugin'))
self.donate_enabled_action.triggered.connect(self._donate_clicked)
self.donate_enabled_action.setEnabled(False)
self.plugin_view.addAction(self.donate_enabled_action)
sep3 = QAction(self)
sep3.setSeparator(True)
self.plugin_view.addAction(sep3)
self.configure_action = QAction(QIcon(I('config.png')), _('&Customize plugin'), self)
self.configure_action.setToolTip(_('Customize the options for this plugin'))
self.configure_action.triggered.connect(self._configure_clicked)
self.configure_action.setEnabled(False)
self.plugin_view.addAction(self.configure_action)
def _finished(self, *args):
if self.model:
update_plugins = list(filter(filter_upgradeable_plugins, self.model.display_plugins))
self.gui.recalc_update_label(len(update_plugins))
def _plugin_current_changed(self, current, previous):
if current.isValid():
actual_idx = self.proxy_model.mapToSource(current)
display_plugin = self.model.display_plugins[actual_idx.row()]
self.description.setText(display_plugin.description)
self.forum_link = display_plugin.forum_link
self.zip_url = display_plugin.zip_url
self.forum_action.setEnabled(bool(self.forum_link))
self.install_button.setEnabled(display_plugin.is_valid_to_install())
self.install_action.setEnabled(self.install_button.isEnabled())
self.uninstall_action.setEnabled(display_plugin.is_installed())
self.configure_button.setEnabled(display_plugin.is_installed())
self.configure_action.setEnabled(self.configure_button.isEnabled())
self.toggle_enabled_action.setEnabled(display_plugin.is_installed())
self.donate_enabled_action.setEnabled(bool(display_plugin.donation_link))
else:
self.description.setText('')
self.forum_link = None
self.zip_url = None
self.forum_action.setEnabled(False)
self.install_button.setEnabled(False)
self.install_action.setEnabled(False)
self.uninstall_action.setEnabled(False)
self.configure_button.setEnabled(False)
self.configure_action.setEnabled(False)
self.toggle_enabled_action.setEnabled(False)
self.donate_enabled_action.setEnabled(False)
self.update_forum_label()
def _donate_clicked(self):
plugin = self._selected_display_plugin()
if plugin and plugin.donation_link:
open_url(QUrl(plugin.donation_link))
def _select_and_focus_view(self, change_selection=True):
if change_selection and self.plugin_view.model().rowCount() > 0:
self.plugin_view.selectRow(0)
else:
idx = self.plugin_view.selectionModel().currentIndex()
self._plugin_current_changed(idx, 0)
self.plugin_view.setFocus()
def _filter_combo_changed(self, idx):
self.filter_by_name_lineedit.setText("") # clear the name filter text when a different group was selected
self.proxy_model.set_filter_criteria(idx)
if idx == FILTER_NOT_INSTALLED:
self.plugin_view.sortByColumn(5, Qt.SortOrder.DescendingOrder)
else:
self.plugin_view.sortByColumn(0, Qt.SortOrder.AscendingOrder)
self._select_and_focus_view()
def _filter_name_lineedit_changed(self, text):
self.proxy_model.set_filter_text(text) # set the filter text for filterAcceptsRow
def _forum_label_activated(self):
if self.forum_link:
open_url(QUrl(self.forum_link))
def _selected_display_plugin(self):
idx = self.plugin_view.selectionModel().currentIndex()
actual_idx = self.proxy_model.mapToSource(idx)
return self.model.display_plugins[actual_idx.row()]
def _uninstall_plugin(self, name_to_remove):
if DEBUG:
prints('Removing plugin: ', name_to_remove)
remove_plugin(name_to_remove)
# Make sure that any other plugins that required this plugin
# to be uninstalled first have the requirement removed
for display_plugin in self.model.display_plugins:
# Make sure we update the status and display of the
# plugin we just uninstalled
if name_to_remove in display_plugin.uninstall_plugins:
if DEBUG:
prints('Removing uninstall dependency for: ', display_plugin.name)
display_plugin.uninstall_plugins.remove(name_to_remove)
if display_plugin.qname == name_to_remove:
if DEBUG:
prints('Resetting plugin to uninstalled status: ', display_plugin.name)
display_plugin.installed_version = None
display_plugin.plugin = None
display_plugin.uninstall_plugins = []
if self.proxy_model.filter_criteria not in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
self.model.refresh_plugin(display_plugin)
def _uninstall_clicked(self):
display_plugin = self._selected_display_plugin()
if not question_dialog(self, _('Are you sure?'), '<p>'+
_('Are you sure you want to uninstall the <b>%s</b> plugin?')%display_plugin.name,
show_copy_button=False):
return
self._uninstall_plugin(display_plugin.qname)
if self.proxy_model.filter_criteria in [FILTER_INSTALLED, FILTER_UPDATE_AVAILABLE]:
self.model.beginResetModel(), self.model.endResetModel()
self._select_and_focus_view()
else:
self._select_and_focus_view(change_selection=False)
def _install_clicked(self):
display_plugin = self._selected_display_plugin()
if not question_dialog(self, _('Install %s')%display_plugin.name, '<p>' +
_('Installing plugins is a <b>security risk</b>. '
'Plugins can contain a virus/malware. '
'Only install it if you got it from a trusted source.'
' Are you sure you want to proceed?'),
show_copy_button=False):
return
if display_plugin.uninstall_plugins:
uninstall_names = list(display_plugin.uninstall_plugins)
if DEBUG:
prints('Uninstalling plugin: ', ', '.join(uninstall_names))
for name_to_remove in uninstall_names:
self._uninstall_plugin(name_to_remove)
plugin_zip_url = display_plugin.zip_url
if DEBUG:
prints('Downloading plugin ZIP attachment: ', plugin_zip_url)
self.gui.status_bar.showMessage(_('Downloading plugin ZIP attachment: %s') % plugin_zip_url)
zip_path = self._download_zip(plugin_zip_url)
if DEBUG:
prints('Installing plugin: ', zip_path)
self.gui.status_bar.showMessage(_('Installing plugin: %s') % zip_path)
do_restart = False
try:
from calibre.customize.ui import config
installed_plugins = frozenset(config['plugins'])
try:
plugin = add_plugin(zip_path)
except NameConflict as e:
return error_dialog(self.gui, _('Already exists'),
str(e), show=True)
# Check for any toolbars to add to.
widget = ConfigWidget(self.gui)
widget.gui = self.gui
widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins)
self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name)
d = info_dialog(self.gui, _('Success'),
_('Plugin <b>{0}</b> successfully installed under <b>'
'{1}</b>. You may have to restart calibre '
'for the plugin to take effect.').format(plugin.name, plugin.type),
show_copy_button=False)
b = d.bb.addButton(_('&Restart calibre now'), QDialogButtonBox.ButtonRole.AcceptRole)
b.setIcon(QIcon(I('lt.png')))
d.do_restart = False
def rf():
d.do_restart = True
b.clicked.connect(rf)
d.set_details('')
d.exec()
b.clicked.disconnect()
do_restart = d.do_restart
display_plugin.plugin = plugin
# We cannot read the 'actual' version information as the plugin will not be loaded yet
display_plugin.installed_version = display_plugin.available_version
except:
if DEBUG:
prints('ERROR occurred while installing plugin: %s'%display_plugin.name)
traceback.print_exc()
error_dialog(self.gui, _('Install plugin failed'),
_('A problem occurred while installing this plugin.'
' This plugin will now be uninstalled.'
' Please post the error message in details below into'
' the forum thread for this plugin and restart calibre.'),
det_msg=traceback.format_exc(), show=True)
if DEBUG:
prints('Due to error now uninstalling plugin: %s'%display_plugin.name)
remove_plugin(display_plugin.name)
display_plugin.plugin = None
display_plugin.uninstall_plugins = []
if self.proxy_model.filter_criteria in [FILTER_NOT_INSTALLED, FILTER_UPDATE_AVAILABLE]:
self.model.beginResetModel(), self.model.endResetModel()
self._select_and_focus_view()
else:
self.model.refresh_plugin(display_plugin)
self._select_and_focus_view(change_selection=False)
if do_restart:
self.do_restart = True
self.accept()
def _configure_clicked(self):
display_plugin = self._selected_display_plugin()
plugin = display_plugin.plugin
if not plugin.is_customizable():
return info_dialog(self, _('Plugin not customizable'),
_('Plugin: %s does not need customization')%plugin.name, show=True)
from calibre.customize import InterfaceActionBase
if isinstance(plugin, InterfaceActionBase) and not getattr(plugin,
'actual_iaction_plugin_loaded', False):
return error_dialog(self, _('Must restart'),
_('You must restart calibre before you can'
' configure the <b>%s</b> plugin')%plugin.name, show=True)
plugin.do_user_config(self.parent())
def _toggle_enabled_clicked(self):
display_plugin = self._selected_display_plugin()
plugin = display_plugin.plugin
if not plugin.can_be_disabled:
return error_dialog(self,_('Plugin cannot be disabled'),
_('The plugin: %s cannot be disabled')%plugin.name, show=True)
if is_disabled(plugin):
enable_plugin(plugin)
else:
disable_plugin(plugin)
self.model.refresh_plugin(display_plugin)
def _download_zip(self, plugin_zip_url):
from calibre.ptempfile import PersistentTemporaryFile
raw = get_https_resource_securely(plugin_zip_url, headers={'User-Agent':f'{__appname__} {__version__}'})
with PersistentTemporaryFile('.zip') as pt:
pt.write(raw)
return pt.name