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 gmSurgery 
 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 '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 shutil.copy2(_logfile_name, new_name)
251 # ------------------------------------------------------------------------
252 -def install_wx_exception_handler():
253 254 global _logfile_name 255 _logfile_name = gmLog2._logfile_name 256 257 global _local_account 258 _local_account = os.path.basename(os.path.expanduser('~')) 259 260 set_helpdesk(gmSurgery.gmCurrentPractice().helpdesk) 261 set_staff_name(_local_account) 262 set_is_public_database(False) 263 set_sender_email(None) 264 set_client_version('gmExceptionHandlingWidgets.py <default>') 265 266 gmDispatcher.connect(signal = 'application_closing', receiver = _on_application_closing) 267 268 global _prev_excepthook 269 _prev_excepthook = sys.excepthook 270 sys.excepthook = handle_uncaught_exception_wx 271 272 return True
273 # ------------------------------------------------------------------------
274 -def uninstall_wx_exception_handler():
275 if _prev_excepthook is None: 276 sys.excepthook = sys.__excepthook__ 277 return True 278 sys.excepthook = _prev_excepthook 279 return True
280 # ------------------------------------------------------------------------
281 -def _on_application_closing():
282 global application_is_closing 283 # used to ignore a few exceptions, such as when the 284 # C++ object has been destroyed before the Python one 285 application_is_closing = True
286 # ========================================================================
287 -def mail_log(parent=None, comment=None, helpdesk=None, sender=None):
288 289 if (comment is None) or (comment.strip() == u''): 290 comment = wx.GetTextFromUser ( 291 message = _( 292 'Please enter a short note on what you\n' 293 'were about to do in GNUmed:' 294 ), 295 caption = _('Sending bug report'), 296 parent = parent 297 ) 298 if comment.strip() == u'': 299 comment = u'<user did not comment on bug report>' 300 301 receivers = [] 302 if helpdesk is not None: 303 receivers = regex.findall ( 304 '[\S]+@[\S]+', 305 helpdesk.strip(), 306 flags = regex.UNICODE | regex.LOCALE 307 ) 308 if len(receivers) == 0: 309 if _is_public_database: 310 receivers = [u'gnumed-bugs@gnu.org'] 311 312 receiver_string = wx.GetTextFromUser ( 313 message = _( 314 'Edit the list of email addresses to send the\n' 315 'bug report to (separate addresses by spaces).\n' 316 '\n' 317 'Note that <gnumed-bugs@gnu.org> refers to\n' 318 'the public (!) GNUmed bugs mailing list.' 319 ), 320 caption = _('Sending bug report'), 321 default_value = ','.join(receivers), 322 parent = parent 323 ) 324 if receiver_string.strip() == u'': 325 return 326 327 receivers = regex.findall ( 328 '[\S]+@[\S]+', 329 receiver_string, 330 flags = regex.UNICODE | regex.LOCALE 331 ) 332 333 dlg = gmGuiHelpers.c2ButtonQuestionDlg ( 334 parent, 335 -1, 336 caption = _('Sending bug report'), 337 question = _( 338 'Your bug report will be sent to:\n' 339 '\n' 340 '%s\n' 341 '\n' 342 'Make sure you have reviewed the log file for potentially\n' 343 'sensitive information before sending out the bug report.\n' 344 '\n' 345 'Note that emailing the report may take a while depending\n' 346 'on the speed of your internet connection.\n' 347 ) % u'\n'.join(receivers), 348 button_defs = [ 349 {'label': _('Send report'), 'tooltip': _('Yes, send the bug report.')}, 350 {'label': _('Cancel'), 'tooltip': _('No, do not send the bug report.')} 351 ], 352 show_checkbox = True, 353 checkbox_msg = _('include log file in bug report') 354 ) 355 dlg._CHBOX_dont_ask_again.SetValue(_is_public_database) 356 go_ahead = dlg.ShowModal() 357 if go_ahead == wx.ID_NO: 358 dlg.Destroy() 359 return 360 361 include_log = dlg._CHBOX_dont_ask_again.GetValue() 362 if not _is_public_database: 363 if include_log: 364 result = gmGuiHelpers.gm_show_question ( 365 _( 366 'The database you are connected to is marked as\n' 367 '"in-production with controlled access".\n' 368 '\n' 369 'You indicated that you want to include the log\n' 370 'file in your bug report. While this is often\n' 371 'useful for debugging the log file might contain\n' 372 'bits of patient data which must not be sent out\n' 373 'without de-identification.\n' 374 '\n' 375 'Please confirm that you want to include the log !' 376 ), 377 _('Sending bug report') 378 ) 379 include_log = (result is True) 380 381 if sender is None: 382 sender = _('<not supplied>') 383 else: 384 if sender.strip() == u'': 385 sender = _('<not supplied>') 386 387 msg = u"""\ 388 Report sent via GNUmed's handler for unexpected exceptions. 389 390 user comment : %s 391 392 client version: %s 393 394 system account: %s 395 staff member : %s 396 sender email : %s 397 398 # enable Launchpad bug tracking 399 affects gnumed 400 tag automatic-report 401 importance medium 402 403 """ % (comment, _client_version, _local_account, _staff_name, sender) 404 if include_log: 405 _log2.error(comment) 406 _log2.warning('syncing log file for emailing') 407 gmLog2.flush() 408 attachments = [ [_logfile_name, 'text/plain', 'quoted-printable'] ] 409 else: 410 attachments = None 411 412 dlg.Destroy() 413 414 wx.BeginBusyCursor() 415 try: 416 gmNetworkTools.send_mail ( 417 sender = '%s <%s>' % (_staff_name, gmNetworkTools.default_mail_sender), 418 receiver = receivers, 419 subject = u'<bug>: %s' % comment, 420 message = msg, 421 encoding = gmI18N.get_encoding(), 422 server = gmNetworkTools.default_mail_server, 423 auth = {'user': gmNetworkTools.default_mail_sender, 'password': u'gnumed-at-gmx-net'}, 424 attachments = attachments 425 ) 426 gmDispatcher.send(signal='statustext', msg = _('Bug report has been emailed.')) 427 except: 428 _log2.exception('cannot send bug report') 429 gmDispatcher.send(signal='statustext', msg = _('Bug report COULD NOT be emailed.')) 430 wx.EndBusyCursor()
431 432 # ======================================================================== 433 from Gnumed.wxGladeWidgets import wxgUnhandledExceptionDlg 434
435 -class cUnhandledExceptionDlg(wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg):
436
437 - def __init__(self, *args, **kwargs):
438 439 exception = kwargs['exception'] 440 del kwargs['exception'] 441 self.logfile = kwargs['logfile'] 442 del kwargs['logfile'] 443 444 wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg.__init__(self, *args, **kwargs) 445 446 if _sender_email is not None: 447 self._TCTRL_sender.SetValue(_sender_email) 448 self._TCTRL_helpdesk.SetValue(_helpdesk) 449 self._TCTRL_logfile.SetValue(self.logfile) 450 t, v, tb = exception 451 self._TCTRL_exc_type.SetValue(str(t)) 452 self._TCTRL_exc_value.SetValue(str(v)) 453 self._TCTRL_traceback.SetValue(''.join(traceback.format_tb(tb))) 454 455 self.Fit()
456 #------------------------------------------
457 - def _on_close_gnumed_button_pressed(self, evt):
458 comment = self._TCTRL_comment.GetValue() 459 if (comment is not None) and (comment.strip() != u''): 460 _log2.error(u'user comment: %s', comment.strip()) 461 _log2.warning('syncing log file for backup to [%s]', self.logfile) 462 gmLog2.flush() 463 try: 464 shutil.copy2(_logfile_name, self.logfile) 465 except IOError: 466 _log2.error('cannot backup log file') 467 top_win = wx.GetApp().GetTopWindow() 468 wx.CallAfter(top_win.Close) 469 evt.Skip()
470 #------------------------------------------
471 - def _on_mail_button_pressed(self, evt):
472 473 mail_log ( 474 parent = self, 475 comment = self._TCTRL_comment.GetValue().strip(), 476 helpdesk = self._TCTRL_helpdesk.GetValue().strip(), 477 sender = self._TCTRL_sender.GetValue().strip() 478 ) 479 480 evt.Skip()
481 #------------------------------------------
482 - def _on_view_log_button_pressed(self, evt):
483 from Gnumed.pycommon import gmMimeLib 484 gmLog2.flush() 485 gmMimeLib.call_viewer_on_file(_logfile_name, block = False) 486 evt.Skip()
487 # ======================================================================== 488