%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/ |
| Current File : //lib/calibre/calibre/gui2/update.py |
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re, ssl, json
from threading import Thread, Event
from qt.core import (QObject, pyqtSignal, Qt, QUrl, QDialog, QGridLayout,
QLabel, QCheckBox, QDialogButtonBox, QIcon)
from calibre.constants import (__appname__, __version__, iswindows, ismacos,
isportable, is64bit, numeric_version)
from calibre import prints, as_unicode
from calibre.utils.config import prefs
from calibre.utils.localization import localize_website_link
from calibre.utils.https import get_https_resource_securely
from calibre.gui2 import config, dynamic, open_url
from calibre.gui2.dialogs.plugin_updater import get_plugin_updates_available
from calibre.utils.serialize import msgpack_dumps, msgpack_loads
from polyglot.binary import as_hex_unicode, from_hex_bytes
URL = 'https://code.calibre-ebook.com/latest'
# URL = 'http://localhost:8000/latest'
NO_CALIBRE_UPDATE = (0, 0, 0)
def get_download_url():
which = ('portable' if isportable else 'windows' if iswindows
else 'osx' if ismacos else 'linux')
if which == 'windows' and is64bit:
which += '64'
return localize_website_link('https://calibre-ebook.com/download_' + which)
def get_newest_version():
return NO_CALIBRE_UPDATE
try:
icon_theme_name = json.loads(I('icon-theme.json', data=True))['name']
except Exception:
icon_theme_name = ''
headers={
'CALIBRE-VERSION':__version__,
'CALIBRE-OS': ('win' if iswindows else 'osx' if ismacos else 'oth'),
'CALIBRE-INSTALL-UUID': prefs['installation_uuid'],
'CALIBRE-ICON-THEME': icon_theme_name,
}
try:
version = get_https_resource_securely(URL, headers=headers)
except ssl.SSLError as err:
if getattr(err, 'reason', None) != 'CERTIFICATE_VERIFY_FAILED':
raise
# certificate verification failed, since the version check contains no
# critical information, ignore and proceed
# We have to do this as if the calibre CA certificate ever
# needs to be revoked, then we won't be able to do version checks
version = get_https_resource_securely(URL, headers=headers, cacerts=None)
try:
version = version.decode('utf-8').strip()
except UnicodeDecodeError:
version = ''
ans = NO_CALIBRE_UPDATE
m = re.match(r'(\d+)\.(\d+).(\d+)$', version)
if m is not None:
ans = tuple(map(int, (m.group(1), m.group(2), m.group(3))))
return ans
class Signal(QObject):
update_found = pyqtSignal(object, object)
class CheckForUpdates(Thread):
INTERVAL = 24*60*60 # seconds
daemon = True
def __init__(self, parent):
Thread.__init__(self)
self.shutdown_event = Event()
self.signal = Signal(parent)
def run(self):
while not self.shutdown_event.is_set():
calibre_update_version = NO_CALIBRE_UPDATE
plugins_update_found = 0
try:
version = get_newest_version()
if version[:2] > numeric_version[:2]:
calibre_update_version = version
except Exception as e:
prints('Failed to check for calibre update:', as_unicode(e))
try:
update_plugins = get_plugin_updates_available(raise_error=True)
if update_plugins is not None:
plugins_update_found = len(update_plugins)
except Exception as e:
prints('Failed to check for plugin update:', as_unicode(e))
if calibre_update_version != NO_CALIBRE_UPDATE or plugins_update_found > 0:
self.signal.update_found.emit(calibre_update_version, plugins_update_found)
self.shutdown_event.wait(self.INTERVAL)
def shutdown(self):
self.shutdown_event.set()
def version_key(calibre_version):
if isinstance(calibre_version, bytes):
calibre_version = calibre_version.decode('utf-8')
if calibre_version.count('.') > 1:
calibre_version = calibre_version.rpartition('.')[0]
return calibre_version
def is_version_notified(calibre_version):
key = version_key(calibre_version)
done = dynamic.get('notified-version-updates') or set()
return key in done
def save_version_notified(calibre_version):
done = dynamic.get('notified-version-updates') or set()
done.add(version_key(calibre_version))
dynamic.set('notified-version-updates', done)
class UpdateNotification(QDialog):
def __init__(self, calibre_version, plugin_updates, parent=None):
QDialog.__init__(self, parent)
self.setAttribute(Qt.WidgetAttribute.WA_QuitOnClose, False)
self.resize(400, 250)
self.l = QGridLayout()
self.setLayout(self.l)
self.logo = QLabel()
self.logo.setMaximumWidth(110)
self.logo.setPixmap(QIcon(I('lt.png')).pixmap(100, 100))
ver = calibre_version
if ver.endswith('.0'):
ver = ver[:-2]
self.label = QLabel('<p>'+ _(
'New version <b>{ver}</b> of {app} is available for download. '
'See the <a href="{url}">new features</a>.').format(
url=localize_website_link('https://calibre-ebook.com/whats-new'),
app=__appname__, ver=ver))
self.label.setOpenExternalLinks(True)
self.label.setWordWrap(True)
self.setWindowTitle(_('Update available!'))
self.setWindowIcon(QIcon(I('lt.png')))
self.l.addWidget(self.logo, 0, 0)
self.l.addWidget(self.label, 0, 1)
self.cb = QCheckBox(
_('Show this notification for future updates'), self)
self.l.addWidget(self.cb, 1, 0, 1, -1)
self.cb.setChecked(config.get('new_version_notification'))
self.cb.stateChanged.connect(self.show_future)
self.bb = QDialogButtonBox(self)
b = self.bb.addButton(_('&Get update'), QDialogButtonBox.ButtonRole.AcceptRole)
b.setDefault(True)
b.setIcon(QIcon(I('arrow-down.png')))
if plugin_updates > 0:
b = self.bb.addButton(_('Update &plugins'), QDialogButtonBox.ButtonRole.ActionRole)
b.setIcon(QIcon(I('plugins/plugin_updater.png')))
b.clicked.connect(self.get_plugins, type=Qt.ConnectionType.QueuedConnection)
self.bb.addButton(QDialogButtonBox.StandardButton.Cancel)
self.l.addWidget(self.bb, 2, 0, 1, -1)
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
save_version_notified(calibre_version)
def get_plugins(self):
from calibre.gui2.dialogs.plugin_updater import (PluginUpdaterDialog,
FILTER_UPDATE_AVAILABLE)
d = PluginUpdaterDialog(self.parent(),
initial_filter=FILTER_UPDATE_AVAILABLE)
d.exec()
if d.do_restart:
QDialog.accept(self)
from calibre.gui2.ui import get_gui
gui = get_gui()
if gui is not None:
gui.quit(restart=True)
def show_future(self, *args):
config.set('new_version_notification', bool(self.cb.isChecked()))
def accept(self):
open_url(QUrl(get_download_url()))
QDialog.accept(self)
class UpdateMixin:
def __init__(self, *args, **kw):
pass
def init_update_mixin(self, opts):
self.last_newest_calibre_version = NO_CALIBRE_UPDATE
if not opts.no_update_check:
self.update_checker = CheckForUpdates(self)
self.update_checker.signal.update_found.connect(self.update_found,
type=Qt.ConnectionType.QueuedConnection)
self.update_checker.start()
def recalc_update_label(self, number_of_plugin_updates):
self.update_found(self.last_newest_calibre_version, number_of_plugin_updates)
def update_found(self, calibre_version, number_of_plugin_updates, force=False, no_show_popup=False):
self.last_newest_calibre_version = calibre_version
has_calibre_update = calibre_version != NO_CALIBRE_UPDATE and calibre_version[0] > 0
has_plugin_updates = number_of_plugin_updates > 0
self.plugin_update_found(number_of_plugin_updates)
version_url = as_hex_unicode(msgpack_dumps((calibre_version, number_of_plugin_updates)))
calibre_version = '.'.join(map(str, calibre_version))
if not has_calibre_update and not has_plugin_updates:
self.status_bar.update_label.setVisible(False)
return
if has_calibre_update:
plt = ''
if has_plugin_updates:
plt = ngettext(' and one plugin update', ' and {} plugin updates', number_of_plugin_updates).format(number_of_plugin_updates)
msg = ('<span style="color:green; font-weight: bold">%s: '
'<a href="update:%s">%s%s</a></span>') % (
_('Update found'), version_url, calibre_version, plt)
else:
plt = ngettext('updated plugin', 'updated plugins', number_of_plugin_updates)
msg = ('<a href="update:%s">%d %s</a>')%(version_url, number_of_plugin_updates, plt)
self.status_bar.update_label.setText(msg)
self.status_bar.update_label.setVisible(True)
if has_calibre_update:
if (force or (config.get('new_version_notification') and not is_version_notified(calibre_version))):
if not no_show_popup:
self._update_notification__ = UpdateNotification(calibre_version,
number_of_plugin_updates, parent=self)
self._update_notification__.show()
elif has_plugin_updates:
if force:
from calibre.gui2.dialogs.plugin_updater import (PluginUpdaterDialog,
FILTER_UPDATE_AVAILABLE)
d = PluginUpdaterDialog(self,
initial_filter=FILTER_UPDATE_AVAILABLE)
d.exec()
if d.do_restart:
self.quit(restart=True)
def plugin_update_found(self, number_of_updates):
# Change the plugin icon to indicate there are updates available
plugin = self.iactions.get('Plugin Updater', None)
if not plugin:
return
if number_of_updates:
plugin.qaction.setText(_('Plugin updates')+'*')
plugin.qaction.setIcon(QIcon(I('plugins/plugin_updater_updates.png')))
plugin.qaction.setToolTip(
ngettext('A plugin update is available',
'There are {} plugin updates available', number_of_updates).format(number_of_updates))
else:
plugin.qaction.setText(_('Plugin updates'))
plugin.qaction.setIcon(QIcon(I('plugins/plugin_updater.png')))
plugin.qaction.setToolTip(_('Install and configure user plugins'))
def update_link_clicked(self, url):
url = str(url)
if url.startswith('update:'):
calibre_version, number_of_plugin_updates = msgpack_loads(from_hex_bytes(url[len('update:'):]))
self.update_found(calibre_version, number_of_plugin_updates, force=True)
if __name__ == '__main__':
from calibre.gui2 import Application
app = Application([])
UpdateNotification('x.y.z', False).exec()
del app