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