4735: Check for updates in a separate thread
svn: r22926
This commit is contained in:
parent
f9540a469f
commit
94d0a21d08
@ -31,11 +31,20 @@ General utility functions useful for the generic plugin system
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import datetime
|
||||||
if sys.version_info[0] < 3:
|
if sys.version_info[0] < 3:
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
else:
|
else:
|
||||||
from io import StringIO, BytesIO
|
from io import StringIO, BytesIO
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# set up logging
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
import logging
|
||||||
|
LOG = logging.getLogger(".gen.plug")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Gramps modules
|
# Gramps modules
|
||||||
@ -44,7 +53,10 @@ else:
|
|||||||
from ._pluginreg import make_environment
|
from ._pluginreg import make_environment
|
||||||
from ..const import USER_PLUGINS
|
from ..const import USER_PLUGINS
|
||||||
from ...version import VERSION_TUPLE
|
from ...version import VERSION_TUPLE
|
||||||
|
from . import BasePluginManager
|
||||||
from ..utils.file import get_unicode_path_from_file_chooser
|
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
|
from ..const import GRAMPS_LOCALE as glocale
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
|
|
||||||
@ -166,6 +178,90 @@ class Zipfile(object):
|
|||||||
"""
|
"""
|
||||||
return os.path.split(name)[1]
|
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):
|
def load_addon_file(path, callback=None):
|
||||||
"""
|
"""
|
||||||
Load an addon from a particular path (from URL or file system).
|
Load an addon from a particular path (from URL or file system).
|
||||||
|
@ -68,6 +68,9 @@ from .managedwindow import ManagedWindow
|
|||||||
from .widgets import MarkupLabel, BasicLabel
|
from .widgets import MarkupLabel, BasicLabel
|
||||||
from .dialog import ErrorDialog, QuestionDialog2, OkDialog
|
from .dialog import ErrorDialog, QuestionDialog2, OkDialog
|
||||||
from .glade import Glade
|
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)
|
table.attach(checkbutton, 0, 3, current_line, current_line+1, yoptions=0)
|
||||||
button = Gtk.Button(_("Check now"))
|
button = Gtk.Button(_("Check now"))
|
||||||
button.connect("clicked", lambda obj: \
|
button.connect("clicked", self.check_for_updates)
|
||||||
self.uistate.viewmanager.check_for_updates(force=True))
|
|
||||||
table.attach(button, 3, 4, current_line, current_line+1, yoptions=0)
|
table.attach(button, 3, 4, current_line, current_line+1, yoptions=0)
|
||||||
|
|
||||||
return _('General'), table
|
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):
|
def add_famtree_panel(self, configdialog):
|
||||||
table = Gtk.Table(2, 2)
|
table = Gtk.Table(2, 2)
|
||||||
table.set_border_width(12)
|
table.set_border_width(12)
|
||||||
|
@ -373,6 +373,7 @@ class DisplayState(Callback):
|
|||||||
'filter-name-changed' : (str, UNITYPE, UNITYPE),
|
'filter-name-changed' : (str, UNITYPE, UNITYPE),
|
||||||
'nameformat-changed' : None,
|
'nameformat-changed' : None,
|
||||||
'grampletbar-close-changed' : None,
|
'grampletbar-close-changed' : None,
|
||||||
|
'update-available' : (list, ),
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav_type to message
|
#nav_type to message
|
||||||
|
@ -34,6 +34,14 @@ import traceback
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# set up logging
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
import logging
|
||||||
|
LOG = logging.getLogger(".gui.plug")
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# GTK modules
|
# GTK modules
|
||||||
@ -58,11 +66,15 @@ from ..utils import open_file_with_default_application
|
|||||||
from ..pluginmanager import GuiPluginManager
|
from ..pluginmanager import GuiPluginManager
|
||||||
from . import tool
|
from . import tool
|
||||||
from ._guioptions import add_gui_options
|
from ._guioptions import add_gui_options
|
||||||
from ..dialog import InfoDialog
|
from ..dialog import InfoDialog, OkDialog
|
||||||
from ..editors import EditPerson
|
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.utils.file import get_unicode_path_from_file_chooser
|
||||||
from gramps.gen.const import URL_WIKISTRING, USER_HOME, WIKI_EXTRAPLUGINS_RAWDATA
|
from gramps.gen.const import URL_WIKISTRING, USER_HOME, WIKI_EXTRAPLUGINS_RAWDATA
|
||||||
from gramps.gen.config import config
|
from gramps.gen.config import config
|
||||||
|
from ..widgets.progressdialog import (LongOpStatus, ProgressMonitor,
|
||||||
|
GtkProgressDialog)
|
||||||
|
|
||||||
def display_message(message):
|
def display_message(message):
|
||||||
"""
|
"""
|
||||||
@ -1050,3 +1062,175 @@ class ToolManagedWindow(tool.Tool, ToolManagedWindowBase):
|
|||||||
tool.Tool.__init__(self, dbstate, options_class, name)
|
tool.Tool.__init__(self, dbstate, options_class, name)
|
||||||
ToolManagedWindowBase.__init__(self, dbstate, uistate, options_class,
|
ToolManagedWindowBase.__init__(self, dbstate, uistate, options_class,
|
||||||
name, callback)
|
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: <GtkTreeIter at 0xbfa89fa0>
|
||||||
|
#path: (8, 0) iter: <GtkTreeIter at 0xbfa89f60>
|
||||||
|
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
|
||||||
|
@ -34,6 +34,7 @@ from __future__ import print_function, division
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import threading
|
||||||
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
||||||
_ = glocale.translation.gettext
|
_ = glocale.translation.gettext
|
||||||
# gtk is not included here, because this file is currently imported
|
# 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 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.lib.person import Person
|
||||||
from gramps.gen.constfunc import has_display, is_quartz, mac, win
|
from gramps.gen.constfunc import has_display, is_quartz, mac, win
|
||||||
from gramps.gen.config import config
|
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
|
from gi.repository import Gdk
|
||||||
color = Gdk.color_parse(hex)
|
color = Gdk.color_parse(hex)
|
||||||
return color
|
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)
|
||||||
|
@ -78,7 +78,7 @@ from gramps.gen.plug import REPORT
|
|||||||
from gramps.gen.plug.report._constants import standalone_categories
|
from gramps.gen.plug.report._constants import standalone_categories
|
||||||
from .plug import (PluginWindows, ReportPluginDialog, ToolPluginDialog)
|
from .plug import (PluginWindows, ReportPluginDialog, ToolPluginDialog)
|
||||||
from .plug.report import report, BookSelector
|
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 .pluginmanager import GuiPluginManager
|
||||||
from gramps.gen.relationship import get_relationship_calculator
|
from gramps.gen.relationship import get_relationship_calculator
|
||||||
from .displaystate import DisplayState, RecentDocsMenu
|
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.constfunc import is_quartz
|
||||||
from gramps.gen.config import config
|
from gramps.gen.config import config
|
||||||
from gramps.gen.errors import WindowActiveError
|
from gramps.gen.errors import WindowActiveError
|
||||||
from .dialog import (ErrorDialog, WarningDialog, QuestionDialog2,
|
from .dialog import ErrorDialog, WarningDialog, QuestionDialog2, InfoDialog
|
||||||
InfoDialog)
|
|
||||||
from .widgets import Statusbar
|
from .widgets import Statusbar
|
||||||
from .undohistory import UndoHistory
|
from .undohistory import UndoHistory
|
||||||
from gramps.gen.utils.file import (media_path_full, get_unicode_path_from_env_var,
|
from gramps.gen.utils.file import (media_path_full, get_unicode_path_from_env_var,
|
||||||
get_unicode_path_from_file_chooser)
|
get_unicode_path_from_file_chooser)
|
||||||
from .dbloader import DbLoader
|
from .dbloader import DbLoader
|
||||||
from .display import display_help, display_url
|
from .display import display_help, display_url
|
||||||
from .widgets.progressdialog import ProgressMonitor, GtkProgressDialog
|
|
||||||
from .configure import GrampsPreferences
|
from .configure import GrampsPreferences
|
||||||
from gramps.gen.db.backup import backup
|
from gramps.gen.db.backup import backup
|
||||||
from gramps.gen.db.exceptions import DbException
|
from gramps.gen.db.exceptions import DbException
|
||||||
from .aboutdialog import GrampsAboutDialog
|
from .aboutdialog import GrampsAboutDialog
|
||||||
from .navigator import Navigator
|
from .navigator import Navigator
|
||||||
from .views.tags import Tags
|
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_KEY = '%s_-_Keybindings' % URL_MANUAL_PAGE
|
||||||
WIKI_HELP_PAGE_MAN = '%s' % 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: <GtkTreeIter at 0xbfa89fa0>
|
|
||||||
#path: (8, 0) iter: <GtkTreeIter at 0xbfa89f60>
|
|
||||||
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
|
# ViewManager
|
||||||
@ -343,17 +314,16 @@ class ViewManager(CLIManager):
|
|||||||
self.rel_class = get_relationship_calculator()
|
self.rel_class = get_relationship_calculator()
|
||||||
self.uistate.set_relationship_class()
|
self.uistate.set_relationship_class()
|
||||||
# Need to call after plugins have been registered
|
# Need to call after plugins have been registered
|
||||||
|
self.uistate.connect('update-available', self.process_updates)
|
||||||
self.check_for_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")
|
howoften = config.get("behavior.check-for-updates")
|
||||||
whattypes = config.get('behavior.check-for-update-types')
|
|
||||||
update = False
|
update = False
|
||||||
if force:
|
if howoften != 0: # update never if zero
|
||||||
update = True
|
|
||||||
elif howoften != 0: # update never if zero
|
|
||||||
y,m,d = list(map(int,
|
y,m,d = list(map(int,
|
||||||
config.get("behavior.last-check-for-updates").split("/")))
|
config.get("behavior.last-check-for-updates").split("/")))
|
||||||
days = (datetime.date.today() - datetime.date(y, m, d)).days
|
days = (datetime.date.today() - datetime.date(y, m, d)).days
|
||||||
@ -365,238 +335,20 @@ class ViewManager(CLIManager):
|
|||||||
update = True
|
update = True
|
||||||
elif howoften == 4: # always
|
elif howoften == 4: # always
|
||||||
update = True
|
update = True
|
||||||
|
|
||||||
if update:
|
if update:
|
||||||
if sys.version_info[0] < 3:
|
AvailableUpdates(self.uistate).start()
|
||||||
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)
|
|
||||||
|
|
||||||
def update_addons(self, addon_update_list):
|
def process_updates(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):
|
|
||||||
"""
|
"""
|
||||||
Select all of the addons for download.
|
Called when add-on updates are available.
|
||||||
"""
|
"""
|
||||||
self.list.model.foreach(update_rows, True)
|
try:
|
||||||
self.list.tree.expand_all()
|
PluginWindows.UpdateAddons(self.uistate, [], addon_update_list)
|
||||||
|
except WindowActiveError:
|
||||||
|
pass
|
||||||
|
|
||||||
def select_none_clicked(self, widget):
|
self.do_reg_plugins(self.dbstate, self.uistate)
|
||||||
"""
|
|
||||||
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()
|
|
||||||
|
|
||||||
def _errordialog(self, title, errormessage):
|
def _errordialog(self, title, errormessage):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user