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