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