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