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