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