4735: Check for updates in a separate thread

svn: r22926
This commit is contained in:
Nick Hall 2013-08-28 23:34:51 +00:00
parent f9540a469f
commit 94d0a21d08
6 changed files with 351 additions and 266 deletions

View File

@ -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).

View File

@ -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)

View File

@ -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

View File

@ -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: <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

View File

@ -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)

View File

@ -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: <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
@ -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):
"""