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

# Source Code for Module Gnumed.wxpython.gmExceptionHandlingWidgets

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