[plugin_installer] Add automatic updates

This commit is contained in:
Daniel Brötzmann
2018-10-17 16:32:28 +02:00
parent dbd48e8796
commit 44c53680c6
2 changed files with 158 additions and 51 deletions

View File

@@ -7,14 +7,66 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_top">12</property> <property name="margin_top">12</property>
<property name="margin_bottom">12</property> <property name="margin_bottom">12</property>
<property name="border_width">18</property>
<property name="row_spacing">6</property>
<child> <child>
<object class="GtkCheckButton" id="check_update"> <object class="GtkCheckButton" id="auto_update_feedback">
<property name="label" translatable="yes">Check update after start</property> <property name="label" translatable="yes">_Show message when automatic update was successful</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">False</property> <property name="receives_default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property> <property name="draw_indicator">True</property>
<signal name="toggled" handler="on_auto_update_feedback_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="auto_update">
<property name="label" translatable="yes">_Update plugins automatically</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_auto_update_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="check_update">
<property name="label" translatable="yes">_Check for updates after start</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="halign">start</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_check_update_toggled" swapped="no"/>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="label" translatable="yes">Plugin updates</property>
<style>
<class name="bold"/>
<class name="dim-label"/>
</style>
</object> </object>
<packing> <packing>
<property name="left_attach">0</property> <property name="left_attach">0</property>

View File

@@ -47,6 +47,7 @@ from gajim.plugins.helpers import get_builder
from gajim.gtk.dialogs import WarningDialog from gajim.gtk.dialogs import WarningDialog
from gajim.gtk.dialogs import HigDialog from gajim.gtk.dialogs import HigDialog
from gajim.gtk.dialogs import YesNoDialog from gajim.gtk.dialogs import YesNoDialog
from gajim.gtk.dialogs import ConfirmationDialogCheck
from gajim.gtkgui_helpers import get_action from gajim.gtkgui_helpers import get_action
log = logging.getLogger('gajim.plugin_system.plugin_installer') log = logging.getLogger('gajim.plugin_system.plugin_installer')
@@ -108,7 +109,9 @@ class PluginInstaller(GajimPlugin):
def init(self): def init(self):
self.description = _('Install and Upgrade Plugins') self.description = _('Install and Upgrade Plugins')
self.config_dialog = PluginInstallerPluginConfigDialog(self) self.config_dialog = PluginInstallerPluginConfigDialog(self)
self.config_default_values = {'check_update': (True, '')} self.config_default_values = {'check_update': (True, ''),
'auto_update': (False, ''),
'auto_update_feedback': (True, '')}
self.gui_extension_points = {'plugin_window': (self.on_activate, None)} self.gui_extension_points = {'plugin_window': (self.on_activate, None)}
self.window = None self.window = None
self.progressbar = None self.progressbar = None
@@ -123,17 +126,20 @@ class PluginInstaller(GajimPlugin):
self.on_activate(app.interface.instances['plugins']) self.on_activate(app.interface.instances['plugins'])
def warn_update(self, plugins): def warn_update(self, plugins):
def open_update(dummy): def open_update(checked):
if checked:
self.config['auto_update'] = True
get_action('plugins').activate() get_action('plugins').activate()
page = self.notebook.page_num(self.available_plugins_box) page = self.notebook.page_num(self.available_plugins_box)
self.notebook.set_current_page(page) self.notebook.set_current_page(page)
if plugins: if plugins:
plugins_str = '\n' + '\n'.join(plugins) plugins_str = '\n' + '\n'.join(plugins)
YesNoDialog( YesNoDialog(
_('Plugins updates'), _('Plugin updates'),
_('Some updates are available for your installer plugins. ' _('There are updates available for plugins you have installed.\n'
'Do you want to update those plugins:\n%s') 'Do you want to update those plugins:\n%s') % plugins_str,
% plugins_str, on_response_yes=open_update) checktext=_('Update plugins automatically next time'),
on_response_yes=open_update)
else: else:
log.info('No updates found') log.info('No updates found')
if hasattr(self, 'thread'): if hasattr(self, 'thread'):
@@ -143,7 +149,8 @@ class PluginInstaller(GajimPlugin):
if hasattr(self, 'thread'): if hasattr(self, 'thread'):
return return
log.info('Checking for Updates...') log.info('Checking for Updates...')
self.start_download(check_update=True) auto_update = self.config['auto_update']
self.start_download(check_update=True, auto_update=auto_update)
self.timeout_id = 0 self.timeout_id = 0
def deactivate(self): def deactivate(self):
@@ -225,19 +232,18 @@ class PluginInstaller(GajimPlugin):
if self.available_plugins_model[i][Column.UPGRADE]: if self.available_plugins_model[i][Column.UPGRADE]:
dir_list.append(self.available_plugins_model[i][Column.DIR]) dir_list.append(self.available_plugins_model[i][Column.DIR])
self.start_download(remote_dirs=dir_list) self.start_download(remote_dirs=dir_list, auto_update=False)
def on_error(self, reason): def on_error(self, reason):
if reason == 'CERTIFICATE_VERIFY_FAILED': if reason == 'CERTIFICATE_VERIFY_FAILED':
YesNoDialog( YesNoDialog(
_('Security error during download'), _('Security error during download'),
_('A security error occurred when ' _('A security error occurred while '
'downloading. The certificate of the ' 'downloading. The certificate of the '
'plugin archive could not be verified. ' 'plugin archive could not be verified. '
'this might be a security attack. ' 'This might be a security attack. '
'\n\nYou can continue at your risk. ' '\n\nYou can continue at your own risk (not recommended). '
'Do you want to do so? ' 'Do you want to continue?'
'(not recommended)'
), ),
on_response_yes=lambda dlg: on_response_yes=lambda dlg:
self.start_download(secure=False, upgrading=True)) self.start_download(secure=False, upgrading=True))
@@ -251,49 +257,66 @@ class PluginInstaller(GajimPlugin):
_('An error occurred when downloading\n\n' _('An error occurred when downloading\n\n'
'<tt>[%s]</tt>' % (str(text))), self.window) '<tt>[%s]</tt>' % (str(text))), self.window)
def start_download(self, secure=True, remote_dirs=False, def start_download(self, secure=True, remote_dirs=False, upgrading=False,
upgrading=False, check_update=False): check_update=False, auto_update=False):
log.info('Start Download...') log.info('Start Download...')
log.debug( log.debug(
'secure: %s, remote_dirs: %s, upgrading: %s, check_update: %s', 'secure: %s, remote_dirs: %s, upgrading: %s, check_update: %s, auto_update: %s',
secure, remote_dirs, upgrading, check_update) secure, remote_dirs, upgrading, check_update, auto_update)
self.thread = DownloadAsync( self.thread = DownloadAsync(
self, secure=secure, remote_dirs=remote_dirs, self, secure=secure, remote_dirs=remote_dirs, upgrading=upgrading,
upgrading=upgrading, check_update=check_update) check_update=check_update, auto_update=auto_update)
self.thread.start() self.thread.start()
def on_plugin_downloaded(self, plugin_dirs): def on_plugin_downloaded(self, plugin_dirs, auto_update):
need_restart = False need_restart = False
for _dir in plugin_dirs: for _dir in plugin_dirs:
updated = app.plugin_manager.update_plugins(replace=False, activate=True, plugin_name=_dir) updated = app.plugin_manager.update_plugins(replace=False, activate=True, plugin_name=_dir)
if updated: if updated:
plugin = app.plugin_manager.get_active_plugin(updated[0]) if not auto_update:
for row in range(len(self.available_plugins_model)): plugin = app.plugin_manager.get_active_plugin(updated[0])
model_row = self.available_plugins_model[row] for row in range(len(self.available_plugins_model)):
if plugin.name == model_row[Column.NAME]: model_row = self.available_plugins_model[row]
model_row[Column.LOCAL_VERSION] = plugin.version if plugin.name == model_row[Column.NAME]:
model_row[Column.UPGRADE] = False model_row[Column.LOCAL_VERSION] = plugin.version
break model_row[Column.UPGRADE] = False
# get plugin icon break
icon_file = os.path.join(plugin.__path__, os.path.split( if not auto_update:
plugin.__path__)[1]) + '.png' # Get plugin icon
icon = FALLBACK_ICON icon_file = os.path.join(plugin.__path__, os.path.split(
if os.path.isfile(icon_file): plugin.__path__)[1]) + '.png'
icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16) icon = FALLBACK_ICON
row = [plugin, plugin.name, True, plugin.activatable, icon] if os.path.isfile(icon_file):
self.installed_plugins_model.append(row) icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16)
row = [plugin, plugin.name, True, plugin.activatable, icon]
self.installed_plugins_model.append(row)
else: else:
need_restart = True need_restart = True
if need_restart: if need_restart:
txt = _('All plugins downloaded.\nThe updates will ' txt = _('All plugins downloaded.\nThe updates will '
'be installed on next Gajim restart.') 'be installed the next time Gajim is started.')
else: else:
txt = _('All selected plugins downloaded and activated') txt = _('All selected plugins downloaded and activated')
dialog = HigDialog( if not auto_update:
self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, '', txt) dialog = HigDialog(
dialog.set_modal(False) self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, '', txt)
dialog.popup() dialog.set_modal(False)
dialog.popup()
if auto_update and self.config['auto_update_feedback']:
def on_ok(checked):
if checked:
self.config['auto_update_feedback'] = False
# Hide cancel button to mimic InfoDialogCheck
ConfirmationDialogCheck(_('Plugins updated'),
_('Plugin updates have successfully been downloaded.'
'Updates will be installed on next Gajim restart.'),
_('Do not show this message again'),
on_response_ok=on_ok).get_widget_for_response(
Gtk.ResponseType.CANCEL).hide()
if auto_update and not self.config['auto_update_feedback']:
log.info('Updates downloaded, will install on next restart')
def available_plugins_treeview_selection_changed(self, treeview_selection): def available_plugins_treeview_selection_changed(self, treeview_selection):
model, iter_ = treeview_selection.get_selected() model, iter_ = treeview_selection.get_selected()
@@ -325,7 +348,8 @@ class PluginInstaller(GajimPlugin):
class DownloadAsync(threading.Thread): class DownloadAsync(threading.Thread):
def __init__(self, plugin, secure, remote_dirs, upgrading, check_update): def __init__(self, plugin, secure, remote_dirs,
upgrading, check_update, auto_update):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.plugin = plugin self.plugin = plugin
self.window = plugin.window self.window = plugin.window
@@ -335,6 +359,7 @@ class DownloadAsync(threading.Thread):
self.upgrading = upgrading self.upgrading = upgrading
self.secure = secure self.secure = secure
self.check_update = check_update self.check_update = check_update
self.auto_update = auto_update
self.pulse = None self.pulse = None
def model_append(self, row): def model_append(self, row):
@@ -355,9 +380,10 @@ class DownloadAsync(threading.Thread):
if self.check_update: if self.check_update:
self.run_check_update() self.run_check_update()
else: else:
GLib.idle_add(self.progressbar.show) if not self.auto_update:
self.pulse = GLib.timeout_add(150, self.progressbar_pulse) GLib.idle_add(self.progressbar.show)
self.run_download_plugin_list() self.pulse = GLib.timeout_add(150, self.progressbar_pulse)
self.run_download_plugin_list()
except urllib.error.URLError as exc: except urllib.error.URLError as exc:
if isinstance(exc.reason, ssl.SSLError): if isinstance(exc.reason, ssl.SSLError):
ssl_reason = exc.reason.reason ssl_reason = exc.reason.reason
@@ -413,7 +439,7 @@ class DownloadAsync(threading.Thread):
pix.close() pix.close()
icon = pix.get_pixbuf() icon = pix.get_pixbuf()
# transform to dictonary # Transform to dictonary
config_dict = {} config_dict = {}
for key, value in config.items('info'): for key, value in config.items('info'):
config_dict[key] = value config_dict[key] = value
@@ -457,6 +483,7 @@ class DownloadAsync(threading.Thread):
def run_check_update(self): def run_check_update(self):
to_update = [] to_update = []
auto_update_list = []
zipbuf = self.download_url(MANIFEST_URL) zipbuf = self.download_url(MANIFEST_URL)
plugin_list = self.parse_manifest(zipbuf) plugin_list = self.parse_manifest(zipbuf)
for plugin in plugin_list: for plugin in plugin_list:
@@ -465,7 +492,17 @@ class DownloadAsync(threading.Thread):
if (V(plugin['version']) > V(local_version)) and \ if (V(plugin['version']) > V(local_version)) and \
self.plugin_is_valid(plugin): self.plugin_is_valid(plugin):
to_update.append(plugin['name']) to_update.append(plugin['name'])
GLib.idle_add(self.plugin.warn_update, to_update) auto_update_list.append(plugin['remote_dir'])
if not self.auto_update:
GLib.idle_add(self.plugin.warn_update, to_update)
else:
if auto_update_list:
self.remote_dirs = auto_update_list
GLib.idle_add(self.download_plugin)
else:
log.info('No updates found')
if hasattr(self.plugin, 'thread'):
del self.plugin.thread
def run_download_plugin_list(self): def run_download_plugin_list(self):
if not self.remote_dirs: if not self.remote_dirs:
@@ -501,7 +538,7 @@ class DownloadAsync(threading.Thread):
os.mkdir(local_dir) os.mkdir(local_dir)
local_dir = os.path.dirname(local_dir) local_dir = os.path.dirname(local_dir)
# downloading zip file # Downloading zip file
try: try:
plugin = posixpath.join(PLUGINS_URL, filename) plugin = posixpath.join(PLUGINS_URL, filename)
buf = self.download_url(plugin) buf = self.download_url(plugin)
@@ -510,7 +547,8 @@ class DownloadAsync(threading.Thread):
continue continue
with ZipFile(buf) as zip_file: with ZipFile(buf) as zip_file:
zip_file.extractall(local_dir) zip_file.extractall(local_dir)
GLib.idle_add(self.plugin.on_plugin_downloaded, self.remote_dirs) GLib.idle_add(self.plugin.on_plugin_downloaded,
self.remote_dirs, self.auto_update)
class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog): class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog):
@@ -523,6 +561,23 @@ class PluginInstallerPluginConfigDialog(GajimPluginConfigDialog):
def on_run(self): def on_run(self):
self.xml.check_update.set_active(self.plugin.config['check_update']) self.xml.check_update.set_active(self.plugin.config['check_update'])
self.xml.auto_update.set_sensitive(self.plugin.config['check_update'])
self.xml.auto_update.set_active(self.plugin.config['auto_update'])
self.xml.auto_update_feedback.set_sensitive(self.plugin.config['check_update'])
self.xml.auto_update_feedback.set_active(self.plugin.config['auto_update_feedback'])
def on_check_update_toggled(self, widget): def on_check_update_toggled(self, widget):
self.plugin.config['check_update'] = widget.get_active() self.plugin.config['check_update'] = widget.get_active()
if not self.plugin.config['check_update']:
self.plugin.config['auto_update'] = False
self.xml.auto_update.set_sensitive(self.plugin.config['check_update'])
self.xml.auto_update.set_active(self.plugin.config['auto_update'])
self.xml.auto_update_feedback.set_sensitive(self.plugin.config['auto_update'])
self.xml.auto_update_feedback.set_active(self.plugin.config['auto_update_feedback'])
def on_auto_update_toggled(self, widget):
self.plugin.config['auto_update'] = widget.get_active()
self.xml.auto_update_feedback.set_sensitive(self.plugin.config['auto_update'])
def on_auto_update_feedback_toggled(self, widget):
self.plugin.config['auto_update_feedback'] = widget.get_active()