Package Gnumed :: Package wxpython :: Module gmExceptionHandlingWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmExceptionHandlingWidgets

      1  """GNUmed exception handling widgets."""
      2  # ========================================================================
      3  __version__ = "$Revision: 1.17 $"
      4  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
      5  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
      6
      7  import logging, exceptions, traceback, re as regex, sys, os, shutil, datetime as pyDT
      8
      9
     10  import wx
     11
     12
     13  from Gnumed.business import gmSurgery
     14  from Gnumed.pycommon import gmDispatcher, gmCfg2, gmI18N, gmLog2, gmPG2
     15  from Gnumed.pycommon import gmNetworkTools
     16  from Gnumed.wxpython import gmGuiHelpers
     17
     18
     19  _log2 = logging.getLogger('gm.gui')
     20  _log2.info(__version__)
     21
     22  _prev_excepthook = None
     23  application_is_closing = False
     24  #=========================================================================
     25 -def set_client_version(version):
     26          global _client_version
     27          _client_version = version
     28  #-------------------------------------------------------------------------
     29 -def set_sender_email(email):
     30          global _sender_email
     31          _sender_email = email
     32  #-------------------------------------------------------------------------
     33 -def set_helpdesk(helpdesk):
     34          global _helpdesk
     35          _helpdesk = helpdesk
     36  #-------------------------------------------------------------------------
     37 -def set_staff_name(staff_name):
     38          global _staff_name
     39          _staff_name = staff_name
     40  #-------------------------------------------------------------------------
     41 -def set_is_public_database(value):
     42          global _is_public_database
     43          _is_public_database = value
     44  #-------------------------------------------------------------------------
     45  # exception handlers
     46  #-------------------------------------------------------------------------
     47 -def __ignore_dead_objects_from_async(t, v, tb):
     48
     49          if t != wx._core.PyDeadObjectError:
     50                  return False
     51
     52          wx.EndBusyCursor()
     53
     54          # try to ignore those, they come about from doing
     55          # async work in wx as Robin tells us
     56          _log2.warning('continuing and hoping for the best')
     57          return True
     58  #-------------------------------------------------------------------------
     59 -def __handle_exceptions_on_shutdown(t, v, tb):
     60
     61          if not application_is_closing:
     62                  return False
     63
     64          # dead object error ?
     65          if t == wx._core.PyDeadObjectError:
     66                  return True
     67
     68          gmLog2.log_stack_trace()
     69          return True
     70  #-------------------------------------------------------------------------
     71 -def __handle_import_error(t, v, tb):
     72
     73          if t != exceptions.ImportError:
     74                  return False
     75
     76          wx.EndBusyCursor()
     77
     78          _log2.error('module [%s] not installed', v)
     79          gmGuiHelpers.gm_show_error (
     80                  aTitle = _('Missing GNUmed module'),
     81                  aMessage = _(
     82                          'GNUmed detected that parts of it are not\n'
     83                          'properly installed. The following message\n'
     84                          'names the missing part:\n'
     85                          '\n'
     86                          ' "%s"\n'
     87                          '\n'
     88                          'Please make sure to get the missing\n'
     89                          'parts installed. Otherwise some of the\n'
     90                          'functionality will not be accessible.'
     91                  ) % v
     92          )
     93          return True
     94  #-------------------------------------------------------------------------
     95 -def __handle_ctrl_c(t, v, tb):
     96
     97          if t != KeyboardInterrupt:
     98                  return False
     99
    100          print "<Ctrl-C>: Shutting down ..."
    101          top_win = wx.GetApp().GetTopWindow()
    102          wx.CallAfter(top_win.Close)
    103          return True
    104  #-------------------------------------------------------------------------
    105 -def __handle_lost_db_connection(t, v, tb):
    106
    107          if t not in [gmPG2.dbapi.OperationalError, gmPG2.dbapi.InterfaceError]:
    108                  return False
    109
    110          try:
    111                  msg = gmPG2.extract_msg_from_pg_exception(exc = v)
    112          except:
    113                  msg = u'cannot extract message from PostgreSQL exception'
    114                  print msg
    115                  print v
    116                  return False
    117
    118          conn_lost = False
    119
    120          if t == gmPG2.dbapi.OperationalError:
    121                  conn_lost = (
    122                          ('erver' in msg)
    123                                  and
    124                          (
    125                                  ('term' in msg)
    126                                          or
    127                                  ('abnorm' in msg)
    128                                          or
    129                                  ('end' in msg)
    130                                          or
    131                                  ('oute' in msg)
    132                          )
    133                  )
    134
    135          if t == gmPG2.dbapi.InterfaceError:
    136                  conn_lost = (
    137                          ('onnect' in msg)
    138                                  and
    139                          (('close' in msg) or ('end' in msg))
    140                  )
    141
    142          if not conn_lost:
    143                  return False
    144
    145          _log2.error('lost connection')
    146          gmLog2.log_stack_trace()
    147          wx.EndBusyCursor()
    148          gmLog2.flush()
    149          gmGuiHelpers.gm_show_error (
    150                  aTitle = _('Lost connection'),
    151                  aMessage = _(
    152                          'Since you were last working in GNUmed,\n'
    153                          'your database connection timed out.\n'
    154                          '\n'
    155                          'This GNUmed session is now expired.\n'
    156                          '\n'
    157                          'You will have to close this client and\n'
    158                          'restart a new GNUmed session.'
    159                  )
    160          )
    161          return True
    162  #-------------------------------------------------------------------------
    163 -def handle_uncaught_exception_wx(t, v, tb):
    164
    165          _log2.debug('unhandled exception caught:', exc_info = (t, v, tb))
    166
    167          if __handle_ctrl_c(t, v, tb):
    168                  return
    169
    170          if __handle_exceptions_on_shutdown(t, v, tb):
    171                  return
    172
    173          if __ignore_dead_objects_from_async(t, v, tb):
    174                  return
    175
    176          if __handle_import_error(t, v, tb):
    177                  return
    178
    179          # other exceptions
    180          _cfg = gmCfg2.gmCfgData()
    181          if _cfg.get(option = 'debug') is False:
    182                  _log2.error('enabling debug mode')
    183                  _cfg.set_option(option = 'debug', value = True)
    184                  root_logger = logging.getLogger()
    185                  root_logger.setLevel(logging.DEBUG)
    186                  _log2.debug('unhandled exception caught:', exc_info = (t, v, tb))
    187
    188          if __handle_lost_db_connection(t, v, tb):
    189                  return
    190
    191          gmLog2.log_stack_trace()
    192
    193          # only do this here or else we can invalidate the stack trace
    194          # by Windows throwing an exception ... |-(
    195          # careful: MSW does reference counting on Begin/End* :-(
    196          wx.EndBusyCursor()
    197
    198          name = os.path.basename(_logfile_name)
    199          name, ext = os.path.splitext(name)
    200          new_name = os.path.expanduser(os.path.join (
    201                  '~',
    202                  'gnumed',
    203                  'logs',
    204                  '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
    205          ))
    206
    207          dlg = cUnhandledExceptionDlg(parent = None, id = -1, exception = (t, v, tb), logfile = new_name)
    208          dlg.ShowModal()
    209          comment = dlg._TCTRL_comment.GetValue()
    210          dlg.Destroy()
    211          if (comment is not None) and (comment.strip() != u''):
    212                  _log2.error(u'user comment: %s', comment.strip())
    213
    214          _log2.warning('syncing log file for backup to [%s]', new_name)
    215          gmLog2.flush()
    216          shutil.copy2(_logfile_name, new_name)
    217  # ------------------------------------------------------------------------
    218 -def install_wx_exception_handler():
    219
    220          global _logfile_name
    221          _logfile_name = gmLog2._logfile_name
    222
    223          global _local_account
    224          _local_account = os.path.basename(os.path.expanduser('~'))
    225
    226          set_helpdesk(gmSurgery.gmCurrentPractice().helpdesk)
    227          set_staff_name(_local_account)
    228          set_is_public_database(False)
    229          set_sender_email(None)
    230          set_client_version('gmExceptionHandlingWidgets.py %s' % __version__)
    231
    232          gmDispatcher.connect(signal = 'application_closing', receiver = _on_application_closing)
    233
    234          global _prev_excepthook
    235          _prev_excepthook = sys.excepthook
    236          sys.excepthook = handle_uncaught_exception_wx
    237
    238          return True
    239  # ------------------------------------------------------------------------
    240 -def uninstall_wx_exception_handler():
    241          if _prev_excepthook is None:
    242                  sys.excepthook = sys.__excepthook__
    243                  return True
    244          sys.excepthook = _prev_excepthook
    245          return True
    246  # ------------------------------------------------------------------------
    247 -def _on_application_closing():
    248          global application_is_closing
    249          # used to ignore a few exceptions, such as when the
    250          # C++ object has been destroyed before the Python one
    251          application_is_closing = True
    252  # ========================================================================
    253 -def mail_log(parent=None, comment=None, helpdesk=None, sender=None):
    254
    255                  if (comment is None) or (comment.strip() == u''):
    256                          comment = wx.GetTextFromUser (
    257                                  message = _(
    258                                          'Please enter a short note on what you\n'
    259                                          'were about to do in GNUmed:'
    260                                  ),
    261                                  caption = _('Sending bug report'),
    262                                  parent = parent
    263                          )
    264                          if comment.strip() == u'':
    265                                  comment = u'<user did not comment on bug report>'
    266
    267                  receivers = []
    268                  if helpdesk is not None:
    269                          receivers = regex.findall (
    270                                  '[\S]+@[\S]+',
    271                                  helpdesk.strip(),
    272                                  flags = regex.UNICODE | regex.LOCALE
    273                          )
    274                  if len(receivers) == 0:
    275                          if _is_public_database:
    276                                  receivers = [u'gnumed-bugs@gnu.org']
    277
    278                  receiver_string = wx.GetTextFromUser (
    279                          message = _(
    280                                  'Edit the list of email addresses to send the\n'
    281                                  'bug report to (separate addresses by spaces).\n'
    282                                  '\n'
    283                                  'Note that <gnumed-bugs@gnu.org> refers to\n'
    284                                  'the public (!) GNUmed bugs mailing list.'
    285                          ),
    286                          caption = _('Sending bug report'),
    287                          default_value = ','.join(receivers),
    288                          parent = parent
    289                  )
    290                  if receiver_string.strip() == u'':
    291                          return
    292
    293                  receivers = regex.findall (
    294                          '[\S]+@[\S]+',
    295                          receiver_string,
    296                          flags = regex.UNICODE | regex.LOCALE
    297                  )
    298
    299                  dlg = gmGuiHelpers.c2ButtonQuestionDlg (
    300                          parent,
    301                          -1,
    302                          caption = _('Sending bug report'),
    303                          question = _(
    304                                  'Your bug report will be sent to:\n'
    305                                  '\n'
    306                                  '%s\n'
    307                                  '\n'
    308                                  'Make sure you have reviewed the log file for potentially\n'
    309                                  'sensitive information before sending out the bug report.\n'
    310                                  '\n'
    311                                  'Note that emailing the report may take a while depending\n'
    312                                  'on the speed of your internet connection.\n'
    313                          ) % u'\n'.join(receivers),
    314                          button_defs = [
    315                                  {'label': _('Send report'), 'tooltip': _('Yes, send the bug report.')},
    316                                  {'label': _('Cancel'), 'tooltip': _('No, do not send the bug report.')}
    317                          ],
    318                          show_checkbox = True,
    319                          checkbox_msg = _('include log file in bug report')
    320                  )
    321                  dlg._CHBOX_dont_ask_again.SetValue(_is_public_database)
    322                  go_ahead = dlg.ShowModal()
    323                  if go_ahead == wx.ID_NO:
    324                          dlg.Destroy()
    325                          return
    326
    327                  include_log = dlg._CHBOX_dont_ask_again.GetValue()
    328                  if not _is_public_database:
    329                          if include_log:
    330                                  result = gmGuiHelpers.gm_show_question (
    331                                          _(
    332                                                  'The database you are connected to is marked as\n'
    333                                                  '"in-production with controlled access".\n'
    334                                                  '\n'
    335                                                  'You indicated that you want to include the log\n'
    336                                                  'file in your bug report. While this is often\n'
    337                                                  'useful for debugging the log file might contain\n'
    338                                                  'bits of patient data which must not be sent out\n'
    339                                                  'without de-identification.\n'
    340                                                  '\n'
    341                                                  'Please confirm that you want to include the log !'
    342                                          ),
    343                                          _('Sending bug report')
    344                                  )
    345                                  include_log = (result is True)
    346
    347                  if sender is None:
    348                          sender = _('<not supplied>')
    349                  else:
    350                          if sender.strip() == u'':
    351                                  sender = _('<not supplied>')
    352
    353                  msg = u"""\
    354  Report sent via GNUmed's handler for unexpected exceptions.
    355
    356  user comment  : %s
    357
    358  client version: %s
    359
    360  system account: %s
    361  staff member  : %s
    362  sender email  : %s
    363
    364   # enable Launchpad bug tracking
    365   affects gnumed
    366   tag automatic-report
    367   importance medium
    368
    369  """ % (comment, _client_version, _local_account, _staff_name, sender)
    370                  if include_log:
    371                          _log2.error(comment)
    372                          _log2.warning('syncing log file for emailing')
    373                          gmLog2.flush()
    374                          attachments = [ [_logfile_name, 'text/plain', 'quoted-printable'] ]
    375                  else:
    376                          attachments = None
    377
    378                  dlg.Destroy()
    379
    380                  wx.BeginBusyCursor()
    381                  try:
    382                          gmNetworkTools.send_mail (
    383                                  sender = '%s <%s>' % (_staff_name, gmNetworkTools.default_mail_sender),
    384                                  receiver = receivers,
    385                                  subject = u'<bug>: %s' % comment,
    386                                  message = msg,
    387                                  encoding = gmI18N.get_encoding(),
    388                                  server = gmNetworkTools.default_mail_server,
    389                                  auth = {'user': gmNetworkTools.default_mail_sender, 'password': u'gnumed-at-gmx-net'},
    390                                  attachments = attachments
    391                          )
    392                          gmDispatcher.send(signal='statustext', msg = _('Bug report has been emailed.'))
    393                  except:
    394                          _log2.exception('cannot send bug report')
    395                          gmDispatcher.send(signal='statustext', msg = _('Bug report COULD NOT be emailed.'))
    396                  wx.EndBusyCursor()
    397
    398  # ========================================================================
    399  from Gnumed.wxGladeWidgets import wxgUnhandledExceptionDlg
    400
    401 -class cUnhandledExceptionDlg(wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg):
    402
    403 -        def __init__(self, *args, **kwargs):
    404
    405                  exception = kwargs['exception']
    406                  del kwargs['exception']
    407                  self.logfile = kwargs['logfile']
    408                  del kwargs['logfile']
    409
    410                  wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg.__init__(self, *args, **kwargs)
    411
    412                  if _sender_email is not None:
    413                          self._TCTRL_sender.SetValue(_sender_email)
    414                  self._TCTRL_helpdesk.SetValue(_helpdesk)
    415                  self._TCTRL_logfile.SetValue(self.logfile)
    416                  t, v, tb = exception
    417                  self._TCTRL_exc_type.SetValue(str(t))
    418                  self._TCTRL_exc_value.SetValue(str(v))
    419                  self._TCTRL_traceback.SetValue(''.join(traceback.format_tb(tb)))
    420
    421                  self.Fit()
    422          #------------------------------------------
    423 -        def _on_close_gnumed_button_pressed(self, evt):
    424                  comment = self._TCTRL_comment.GetValue()
    425                  if (comment is not None) and (comment.strip() != u''):
    426                          _log2.error(u'user comment: %s', comment.strip())
    427                  _log2.warning('syncing log file for backup to [%s]', self.logfile)
    428                  gmLog2.flush()
    429                  shutil.copy2(_logfile_name, self.logfile)
    430                  top_win = wx.GetApp().GetTopWindow()
    431                  wx.CallAfter(top_win.Close)
    432                  evt.Skip()
    433          #------------------------------------------
    434 -        def _on_mail_button_pressed(self, evt):
    435
    436                  mail_log (
    437                          parent = self,
    438                          comment = self._TCTRL_comment.GetValue().strip(),
    439                          helpdesk = self._TCTRL_helpdesk.GetValue().strip(),
    440                          sender = self._TCTRL_sender.GetValue().strip()
    441                  )
    442
    443                  evt.Skip()
    444          #------------------------------------------
    445 -        def _on_view_log_button_pressed(self, evt):
    446                  from Gnumed.pycommon import gmMimeLib
    447                  gmLog2.flush()
    448                  gmMimeLib.call_viewer_on_file(_logfile_name, block = False)
    449                  evt.Skip()
    450  # ========================================================================
    451