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