diff --git a/gramps/gen/plug/utils.py b/gramps/gen/plug/utils.py index c9521f356..dea9e030c 100644 --- a/gramps/gen/plug/utils.py +++ b/gramps/gen/plug/utils.py @@ -31,11 +31,20 @@ General utility functions useful for the generic plugin system #------------------------------------------------------------------------- import sys import os +import datetime if sys.version_info[0] < 3: from StringIO import StringIO else: from io import StringIO, BytesIO +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".gen.plug") + #------------------------------------------------------------------------- # # Gramps modules @@ -44,7 +53,10 @@ else: from ._pluginreg import make_environment from ..const import USER_PLUGINS from ...version import VERSION_TUPLE +from . import BasePluginManager from ..utils.file import get_unicode_path_from_file_chooser +from ..utils.configmanager import safe_eval +from ..config import config from ..const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext @@ -166,6 +178,90 @@ class Zipfile(object): """ return os.path.split(name)[1] +def available_updates(): + whattypes = config.get('behavior.check-for-update-types') + if sys.version_info[0] < 3: + from urllib2 import urlopen + else: + from urllib.request import urlopen + LOG.debug("Checking for updated addons...") + langs = glocale.get_language_list() + langs.append("en") + # now we have a list of languages to try: + fp = None + for lang in langs: + URL = ("%s/listings/addons-%s.txt" % + (config.get("behavior.addons-url"), lang)) + LOG.debug(" trying: %s" % URL) + try: + fp = urlopen(URL, timeout=10) # abort after 10 seconds + except: + try: + URL = ("%s/listings/addons-%s.txt" % + (config.get("behavior.addons-url"), lang[:2])) + fp = urlopen(URL, timeout=10) + except Exception as err: # some error + LOG.warn("Failed to open %s: %s" % (lang, err)) + fp = None + if fp and fp.getcode() == 200: # ok + break + + pmgr = BasePluginManager.get_instance() + addon_update_list = [] + if fp and fp.getcode() == 200: + lines = list(fp.readlines()) + count = 0 + for line in lines: + line = line.decode('utf-8') + try: + plugin_dict = safe_eval(line) + if type(plugin_dict) != type({}): + raise TypeError("Line with addon metadata is not " + "a dictionary") + except: + LOG.warning("Skipped a line in the addon listing: " + + str(line)) + continue + id = plugin_dict["i"] + plugin = pmgr.get_plugin(id) + if plugin: + LOG.debug("Comparing %s > %s" % + (version_str_to_tup(plugin_dict["v"], 3), + version_str_to_tup(plugin.version, 3))) + if (version_str_to_tup(plugin_dict["v"], 3) > + version_str_to_tup(plugin.version, 3)): + LOG.debug(" Downloading '%s'..." % plugin_dict["z"]) + if "update" in whattypes: + if (not config.get('behavior.do-not-show-previously-seen-updates') or + plugin_dict["i"] not in config.get('behavior.previously-seen-updates')): + addon_update_list.append((_("Updated"), + "%s/download/%s" % + (config.get("behavior.addons-url"), + plugin_dict["z"]), + plugin_dict)) + else: + LOG.debug(" '%s' is ok" % plugin_dict["n"]) + else: + LOG.debug(" '%s' is not installed" % plugin_dict["n"]) + if "new" in whattypes: + if (not config.get('behavior.do-not-show-previously-seen-updates') or + plugin_dict["i"] not in config.get('behavior.previously-seen-updates')): + addon_update_list.append((_("New"), + "%s/download/%s" % + (config.get("behavior.addons-url"), + plugin_dict["z"]), + plugin_dict)) + config.set("behavior.last-check-for-updates", + datetime.date.today().strftime("%Y/%m/%d")) + count += 1 + if fp: + fp.close() + else: + LOG.debug("Checking Addons Failed") + LOG.debug("Done checking!") + + return addon_update_list + def load_addon_file(path, callback=None): """ Load an addon from a particular path (from URL or file system). diff --git a/gramps/gui/configure.py b/gramps/gui/configure.py index 2fb118c68..3ae58f26d 100644 --- a/gramps/gui/configure.py +++ b/gramps/gui/configure.py @@ -68,6 +68,9 @@ from .managedwindow import ManagedWindow from .widgets import MarkupLabel, BasicLabel from .dialog import ErrorDialog, QuestionDialog2, OkDialog from .glade import Glade +from gramps.gen.plug.utils import available_updates +from .plug import PluginWindows +from gramps.gen.errors import WindowActiveError #------------------------------------------------------------------------- # @@ -1308,12 +1311,39 @@ class GrampsPreferences(ConfigureDialog): table.attach(checkbutton, 0, 3, current_line, current_line+1, yoptions=0) button = Gtk.Button(_("Check now")) - button.connect("clicked", lambda obj: \ - self.uistate.viewmanager.check_for_updates(force=True)) + button.connect("clicked", self.check_for_updates) table.attach(button, 3, 4, current_line, current_line+1, yoptions=0) return _('General'), table + def check_for_updates(self, button): + try: + addon_update_list = available_updates() + except: + OkDialog(_("Checking Addons Failed"), + _("The addon repository appears to be unavailable. " + "Please try again later."), + self.window) + return + + if len(addon_update_list) > 0: + try: + PluginWindows.UpdateAddons(self.uistate, [], addon_update_list) + except WindowActiveError: + pass + else: + check_types = config.get('behavior.check-for-update-types') + OkDialog(_("There are no available addons of this type"), + _("Checked for '%s'") % + _("' and '").join([_(t) for t in check_types]), + self.window) + + # List of translated strings used here + # Dead code for l10n + _('new'), _('update') + + self.uistate.viewmanager.do_reg_plugins(self.dbstate, self.uistate) + def add_famtree_panel(self, configdialog): table = Gtk.Table(2, 2) table.set_border_width(12) diff --git a/gramps/gui/displaystate.py b/gramps/gui/displaystate.py index 493e8b458..e5efe2b2a 100644 --- a/gramps/gui/displaystate.py +++ b/gramps/gui/displaystate.py @@ -373,6 +373,7 @@ class DisplayState(Callback): 'filter-name-changed' : (str, UNITYPE, UNITYPE), 'nameformat-changed' : None, 'grampletbar-close-changed' : None, + 'update-available' : (list, ), } #nav_type to message diff --git a/gramps/gui/plug/_windows.py b/gramps/gui/plug/_windows.py index cddcf18c9..8409d9513 100644 --- a/gramps/gui/plug/_windows.py +++ b/gramps/gui/plug/_windows.py @@ -34,6 +34,14 @@ import traceback import os import sys +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".gui.plug") + #------------------------------------------------------------------------- # # GTK modules @@ -58,11 +66,15 @@ from ..utils import open_file_with_default_application from ..pluginmanager import GuiPluginManager from . import tool from ._guioptions import add_gui_options -from ..dialog import InfoDialog +from ..dialog import InfoDialog, OkDialog from ..editors import EditPerson +from ..glade import Glade +from ..listmodel import ListModel, NOSORT, TOGGLE from gramps.gen.utils.file import get_unicode_path_from_file_chooser from gramps.gen.const import URL_WIKISTRING, USER_HOME, WIKI_EXTRAPLUGINS_RAWDATA from gramps.gen.config import config +from ..widgets.progressdialog import (LongOpStatus, ProgressMonitor, + GtkProgressDialog) def display_message(message): """ @@ -1050,3 +1062,175 @@ class ToolManagedWindow(tool.Tool, ToolManagedWindowBase): tool.Tool.__init__(self, dbstate, options_class, name) ToolManagedWindowBase.__init__(self, dbstate, uistate, options_class, name, callback) + +#------------------------------------------------------------------------- +# +# UpdateAddons +# +#------------------------------------------------------------------------- +class UpdateAddons(ManagedWindow): + + def __init__(self, uistate, track, addon_update_list): + self.title = _('Available Gramps Updates for Addons') + ManagedWindow.__init__(self, uistate, track, self.__class__) + + glade = Glade("updateaddons.glade") + self.update_dialog = glade.toplevel + self.set_window(self.update_dialog, glade.get_object('title'), + self.title) + self.window.set_size_request(750, 400) + + apply_button = glade.get_object('apply') + cancel_button = glade.get_object('cancel') + select_all = glade.get_object('select_all') + select_all.connect("clicked", self.select_all_clicked) + select_none = glade.get_object('select_none') + select_none.connect("clicked", self.select_none_clicked) + apply_button.connect("clicked", self.install_addons) + cancel_button.connect("clicked", self.close) + + self.list = ListModel(glade.get_object("list"), [ + # name, click?, width, toggle + {"name": _('Select'), + "width": 60, + "type": TOGGLE, + "visible_col": 6, + "editable": True}, # 0 selected? + (_('Type'), 1, 180), # 1 new gramplet + (_('Name'), 2, 200), # 2 name (version) + (_('Description'), 3, 200), # 3 description + ('', NOSORT, 0), # 4 url + ('', NOSORT, 0), # 5 id + {"name": '', "type": TOGGLE}, # 6 visible? bool + ], list_mode="tree") + pos = None + addon_update_list.sort(key=lambda x: "%s %s" % (x[0], x[2]["t"])) + last_category = None + for (status,plugin_url,plugin_dict) in addon_update_list: + count = get_count(addon_update_list, plugin_dict["t"]) + category = _("%(adjective)s: %(addon)s") % { + "adjective": status, + "addon": _(plugin_dict["t"])} + if last_category != category: + last_category = category + node = self.list.add([False, # initially selected? + category, + "", + "", + "", + "", + False]) # checkbox visible? + iter = self.list.add([False, # initially selected? + "%s %s" % (status, _(plugin_dict["t"])), + "%s (%s)" % (plugin_dict["n"], + plugin_dict["v"]), + plugin_dict["d"], + plugin_url, + plugin_dict["i"], + True], node=node) + if pos is None: + pos = iter + if pos: + self.list.selection.select_iter(pos) + self.update_dialog.run() + + def build_menu_names(self, obj): + return (self.title, "") + + def select_all_clicked(self, widget): + """ + Select all of the addons for download. + """ + self.list.model.foreach(update_rows, True) + self.list.tree.expand_all() + + def select_none_clicked(self, widget): + """ + Select none of the addons for download. + """ + self.list.model.foreach(update_rows, False) + self.list.tree.expand_all() + + def install_addons(self, obj): + """ + Process all of the selected addons. + """ + self.update_dialog.hide() + model = self.list.model + + iter = model.get_iter_first() + length = 0 + while iter: + iter = model.iter_next(iter) + if iter: + length += model.iter_n_children(iter) + + longop = LongOpStatus( + _("Downloading and installing selected addons..."), + length, 1, # total, increment-by + can_cancel=True) + pm = ProgressMonitor(GtkProgressDialog, + ("Title", self.window, Gtk.DialogFlags.MODAL)) + pm.add_op(longop) + count = 0 + if not config.get('behavior.do-not-show-previously-seen-updates'): + # reset list + config.get('behavior.previously-seen-updates')[:] = [] + + iter = model.get_iter_first() + while iter: + for rowcnt in range(model.iter_n_children(iter)): + child = model.iter_nth_child(iter, rowcnt) + row = [model.get_value(child, n) for n in range(6)] + if longop.should_cancel(): + break + elif row[0]: # toggle on + load_addon_file(row[4], callback=LOG.debug) + count += 1 + else: # add to list of previously seen, but not installed + if row[5] not in config.get('behavior.previously-seen-updates'): + config.get('behavior.previously-seen-updates').append(row[5]) + longop.heartbeat() + pm._get_dlg()._process_events() + iter = model.iter_next(iter) + + if not longop.was_cancelled(): + longop.end() + if count: + OkDialog(_("Done downloading and installing addons"), + "%s %s" % (glocale.translation.ngettext("%d addon was installed.", + "%d addons were installed.", + count) % count, + _("You need to restart Gramps to see new views.")), + self.window) + else: + OkDialog(_("Done downloading and installing addons"), + _("No addons were installed."), + self.window) + self.close() + +#------------------------------------------------------------------------- +# +# Local Functions +# +#------------------------------------------------------------------------- +def update_rows(model, path, iter, user_data): + """ + Update the rows of a model. + """ + #path: (8,) iter: + #path: (8, 0) iter: + if len(path.get_indices()) == 2: + row = model[path] + row[0] = user_data + model.row_changed(path, iter) + +def get_count(addon_update_list, category): + """ + Get the count of matching category items. + """ + count = 0 + for (status,plugin_url,plugin_dict) in addon_update_list: + if plugin_dict["t"] == category and plugin_url: + count += 1 + return count diff --git a/gramps/gui/utils.py b/gramps/gui/utils.py index 94676fb57..9fc3fce9c 100644 --- a/gramps/gui/utils.py +++ b/gramps/gui/utils.py @@ -34,6 +34,7 @@ from __future__ import print_function, division import os import sys import subprocess +import threading from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext # gtk is not included here, because this file is currently imported @@ -46,6 +47,7 @@ _ = glocale.translation.gettext # #------------------------------------------------------------------------- from gi.repository import PangoCairo +from gi.repository import GLib #------------------------------------------------------------------------- # @@ -55,6 +57,7 @@ from gi.repository import PangoCairo from gramps.gen.lib.person import Person from gramps.gen.constfunc import has_display, is_quartz, mac, win from gramps.gen.config import config +from gramps.gen.plug.utils import available_updates #------------------------------------------------------------------------- # @@ -506,3 +509,22 @@ def hex_to_color(hex): from gi.repository import Gdk color = Gdk.color_parse(hex) return color + +#------------------------------------------------------------------------- +# +# AvailableUpdates +# +#------------------------------------------------------------------------- +class AvailableUpdates(threading.Thread): + def __init__(self, uistate): + threading.Thread.__init__(self) + self.uistate = uistate + self.addon_update_list = [] + + def emit_update_available(self): + self.uistate.emit('update-available', (self.addon_update_list, )) + + def run(self): + self.addon_update_list = available_updates() + if len(self.addon_update_list) > 0: + GLib.idle_add(self.emit_update_available) diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index 0f46b29a9..2eac42a08 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -78,7 +78,7 @@ from gramps.gen.plug import REPORT from gramps.gen.plug.report._constants import standalone_categories from .plug import (PluginWindows, ReportPluginDialog, ToolPluginDialog) from .plug.report import report, BookSelector -from gramps.gen.plug.utils import version_str_to_tup, load_addon_file +from .utils import AvailableUpdates from .pluginmanager import GuiPluginManager from gramps.gen.relationship import get_relationship_calculator from .displaystate import DisplayState, RecentDocsMenu @@ -88,22 +88,19 @@ from gramps.gen.const import (HOME_DIR, ICON, URL_BUGTRACKER, URL_HOMEPAGE, from gramps.gen.constfunc import is_quartz from gramps.gen.config import config from gramps.gen.errors import WindowActiveError -from .dialog import (ErrorDialog, WarningDialog, QuestionDialog2, - InfoDialog) +from .dialog import ErrorDialog, WarningDialog, QuestionDialog2, InfoDialog from .widgets import Statusbar from .undohistory import UndoHistory from gramps.gen.utils.file import (media_path_full, get_unicode_path_from_env_var, get_unicode_path_from_file_chooser) from .dbloader import DbLoader from .display import display_help, display_url -from .widgets.progressdialog import ProgressMonitor, GtkProgressDialog from .configure import GrampsPreferences from gramps.gen.db.backup import backup from gramps.gen.db.exceptions import DbException from .aboutdialog import GrampsAboutDialog from .navigator import Navigator from .views.tags import Tags -from gramps.gen.utils.configmanager import safe_eval #------------------------------------------------------------------------- # @@ -239,32 +236,6 @@ WIKI_HELP_PAGE_FAQ = '%s_-_FAQ' % URL_MANUAL_PAGE WIKI_HELP_PAGE_KEY = '%s_-_Keybindings' % URL_MANUAL_PAGE WIKI_HELP_PAGE_MAN = '%s' % URL_MANUAL_PAGE -#------------------------------------------------------------------------- -# -# Local Functions -# -#------------------------------------------------------------------------- -def update_rows(model, path, iter, user_data): - """ - Update the rows of a model. - """ - #path: (8,) iter: - #path: (8, 0) iter: - if len(path.get_indices()) == 2: - row = model[path] - row[0] = user_data - model.row_changed(path, iter) - -def get_count(addon_update_list, category): - """ - Get the count of matching category items. - """ - count = 0 - for (status,plugin_url,plugin_dict) in addon_update_list: - if plugin_dict["t"] == category and plugin_url: - count += 1 - return count - #------------------------------------------------------------------------- # # ViewManager @@ -343,17 +314,16 @@ class ViewManager(CLIManager): self.rel_class = get_relationship_calculator() self.uistate.set_relationship_class() # Need to call after plugins have been registered + self.uistate.connect('update-available', self.process_updates) self.check_for_updates() - def check_for_updates(self, force=False): + def check_for_updates(self): """ + Check for add-on updates. """ howoften = config.get("behavior.check-for-updates") - whattypes = config.get('behavior.check-for-update-types') update = False - if force: - update = True - elif howoften != 0: # update never if zero + if howoften != 0: # update never if zero y,m,d = list(map(int, config.get("behavior.last-check-for-updates").split("/"))) days = (datetime.date.today() - datetime.date(y, m, d)).days @@ -365,238 +335,20 @@ class ViewManager(CLIManager): update = True elif howoften == 4: # always update = True + if update: - if sys.version_info[0] < 3: - from urllib2 import urlopen - else: - from urllib.request import urlopen - LOG.debug("Checking for updated addons...") - langs = glocale.get_language_list() - langs.append("en") - # now we have a list of languages to try: - fp = None - for lang in langs: - URL = ("%s/listings/addons-%s.txt" % - (config.get("behavior.addons-url"), lang)) - LOG.debug(" trying: %s" % URL) - try: - fp = urlopen(URL, timeout=10) # abort after 10 seconds - except: - try: - URL = ("%s/listings/addons-%s.txt" % - (config.get("behavior.addons-url"), lang[:2])) - fp = urlopen(URL, timeout=10) - except Exception as err: # some error - LOG.warn("Failed to open %s: %s" % (lang, err)) - fp = None - if fp and fp.getcode() == 200: # ok - break - addon_update_list = [] - if fp and fp.getcode() == 200: - lines = list(fp.readlines()) - count = 0 - for line in lines: - line = line.decode('utf-8') - try: - plugin_dict = safe_eval(line) - if type(plugin_dict) != type({}): - raise TypeError("Line with addon metadata is not " - "a dictionary") - except: - LOG.warning("Skipped a line in the addon listing: " + - str(line)) - continue - id = plugin_dict["i"] - plugin = self._pmgr.get_plugin(id) - if plugin: - LOG.debug("Comparing %s > %s" % - (version_str_to_tup(plugin_dict["v"], 3), - version_str_to_tup(plugin.version, 3))) - if (version_str_to_tup(plugin_dict["v"], 3) > - version_str_to_tup(plugin.version, 3)): - LOG.debug(" Downloading '%s'..." % plugin_dict["z"]) - if "update" in whattypes: - if (not config.get('behavior.do-not-show-previously-seen-updates') or - plugin_dict["i"] not in config.get('behavior.previously-seen-updates')): - addon_update_list.append((_("Updated"), - "%s/download/%s" % - (config.get("behavior.addons-url"), - plugin_dict["z"]), - plugin_dict)) - else: - LOG.debug(" '%s' is ok" % plugin_dict["n"]) - else: - LOG.debug(" '%s' is not installed" % plugin_dict["n"]) - if "new" in whattypes: - if (not config.get('behavior.do-not-show-previously-seen-updates') or - plugin_dict["i"] not in config.get('behavior.previously-seen-updates')): - addon_update_list.append((_("New"), - "%s/download/%s" % - (config.get("behavior.addons-url"), - plugin_dict["z"]), - plugin_dict)) - config.set("behavior.last-check-for-updates", - datetime.date.today().strftime("%Y/%m/%d")) - count += 1 - if fp: - fp.close() - else: - from .dialog import OkDialog - OkDialog(_("Checking Addons Failed"), - _("The addon repository appears to be unavailable. Please try again later."), - self.window) - if fp: - fp.close() - return - LOG.debug("Done checking!") - # List of translated strings used here - # Dead code for l10n - _('new'), _('update') - if addon_update_list: - self.update_addons(addon_update_list) - elif force: - from .dialog import OkDialog - OkDialog(_("There are no available addons of this type"), - _("Checked for '%s'") % - _("' and '").join([_(t) for t in config.get('behavior.check-for-update-types')]), - self.window) + AvailableUpdates(self.uistate).start() - def update_addons(self, addon_update_list): - from .glade import Glade - from .managedwindow import set_titles - from .listmodel import ListModel, NOSORT, TOGGLE - glade = Glade("updateaddons.glade") - self.update_dialog = glade.toplevel - set_titles(self.update_dialog, glade.get_object('title'), - _('Available Gramps Updates for Addons')) - apply_button = glade.get_object('apply') - cancel_button = glade.get_object('cancel') - select_all = glade.get_object('select_all') - select_all.connect("clicked", self.select_all_clicked) - select_none = glade.get_object('select_none') - select_none.connect("clicked", self.select_none_clicked) - apply_button.connect("clicked", self.install_addons) - cancel_button.connect("clicked", - lambda obj: self.update_dialog.destroy()) - self.list = ListModel(glade.get_object("list"), [ - # name, click?, width, toggle - {"name": _('Select'), - "width": 60, - "type": TOGGLE, - "visible_col": 6, - "editable": True}, # 0 selected? - (_('Type'), 1, 180), # 1 new gramplet - (_('Name'), 2, 200), # 2 name (version) - (_('Description'), 3, 200), # 3 description - ('', NOSORT, 0), # 4 url - ('', NOSORT, 0), # 5 id - {"name": '', "type": TOGGLE}, # 6 visible? bool - ], list_mode="tree") - pos = None - addon_update_list.sort(key=lambda x: "%s %s" % (x[0], x[2]["t"])) - last_category = None - for (status,plugin_url,plugin_dict) in addon_update_list: - count = get_count(addon_update_list, plugin_dict["t"]) - category = _("%(adjective)s: %(addon)s") % { - "adjective": status, - "addon": _(plugin_dict["t"])} - if last_category != category: - last_category = category - node = self.list.add([False, # initially selected? - category, - "", - "", - "", - "", - False]) # checkbox visible? - iter = self.list.add([False, # initially selected? - "%s %s" % (status, _(plugin_dict["t"])), - "%s (%s)" % (plugin_dict["n"], - plugin_dict["v"]), - plugin_dict["d"], - plugin_url, - plugin_dict["i"], - True], node=node) - if pos is None: - pos = iter - if pos: - self.list.selection.select_iter(pos) - self.update_dialog.run() - - def select_all_clicked(self, widget): + def process_updates(self, addon_update_list): """ - Select all of the addons for download. + Called when add-on updates are available. """ - self.list.model.foreach(update_rows, True) - self.list.tree.expand_all() + try: + PluginWindows.UpdateAddons(self.uistate, [], addon_update_list) + except WindowActiveError: + pass - def select_none_clicked(self, widget): - """ - Select none of the addons for download. - """ - self.list.model.foreach(update_rows, False) - self.list.tree.expand_all() - - def install_addons(self, obj): - """ - Process all of the selected addons. - """ - from .dialog import OkDialog - from .widgets.progressdialog import LongOpStatus - self.update_dialog.hide() - model = self.list.model - - iter = model.get_iter_first() - length = 0 - while iter: - iter = model.iter_next(iter) - if iter: - length += model.iter_n_children(iter) - - longop = LongOpStatus( - _("Downloading and installing selected addons..."), - length, 1, # total, increment-by - can_cancel=True) - pm = ProgressMonitor(GtkProgressDialog, - ("Title", self.window, Gtk.DialogFlags.MODAL)) - pm.add_op(longop) - count = 0 - if not config.get('behavior.do-not-show-previously-seen-updates'): - # reset list - config.get('behavior.previously-seen-updates')[:] = [] - - iter = model.get_iter_first() - while iter: - for rowcnt in range(model.iter_n_children(iter)): - child = model.iter_nth_child(iter, rowcnt) - row = [model.get_value(child, n) for n in range(6)] - if longop.should_cancel(): - break - elif row[0]: # toggle on - load_addon_file(row[4], callback=LOG.debug) - count += 1 - else: # add to list of previously seen, but not installed - if row[5] not in config.get('behavior.previously-seen-updates'): - config.get('behavior.previously-seen-updates').append(row[5]) - longop.heartbeat() - pm._get_dlg()._process_events() - iter = model.iter_next(iter) - - if not longop.was_cancelled(): - longop.end() - if count: - self.do_reg_plugins(self.dbstate, self.uistate) - OkDialog(_("Done downloading and installing addons"), - "%s %s" % (glocale.translation.ngettext("%d addon was installed.", - "%d addons were installed.", - count) % count, - _("You need to restart Gramps to see new views.")), - self.window) - else: - OkDialog(_("Done downloading and installing addons"), - _("No addons were installed."), - self.window) - self.update_dialog.destroy() + self.do_reg_plugins(self.dbstate, self.uistate) def _errordialog(self, title, errormessage): """