| Home | Trees | Indices | Help |
|
|---|
|
|
1 """GNUmed exception handling widgets."""
2 # ========================================================================
3 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
5
6 import logging
7 import traceback
8 import sys
9 import os
10 import shutil
11 import datetime as pyDT
12 import re as regex
13
14
15 import wx
16
17
18 from Gnumed.pycommon import gmDispatcher
19 from Gnumed.pycommon import gmCfg2
20 from Gnumed.pycommon import gmI18N
21 from Gnumed.pycommon import gmLog2
22 from Gnumed.pycommon import gmPG2
23 from Gnumed.pycommon import gmExceptions
24 from Gnumed.pycommon import gmNetworkTools
25 from Gnumed.pycommon.gmTools import u_box_horiz_single
26
27 from Gnumed.business import gmPraxis
28
29 from Gnumed.wxpython import gmGuiHelpers
30
31
32 _log = logging.getLogger('gm.gui')
33
34 _prev_excepthook = None
35 application_is_closing = False
36
37 #=========================================================================
41
42 #-------------------------------------------------------------------------
46
47 #-------------------------------------------------------------------------
51
52 #-------------------------------------------------------------------------
56
57 #-------------------------------------------------------------------------
61
62 #-------------------------------------------------------------------------
63 # exception handlers
64 #-------------------------------------------------------------------------
66
67 if t != RuntimeError:
68 return False
69
70 wx.EndBusyCursor()
71
72 # try to ignore those, they come about from doing
73 # async work in wx as Robin tells us
74 _log.error('RuntimeError = dead object: %s', v)
75 _log.warning('continuing and hoping for the best')
76 return True
77
78 #-------------------------------------------------------------------------
80
81 if not application_is_closing:
82 return False
83
84 # dead object error ?
85 if t == RuntimeError:
86 return True
87
88 gmLog2.log_stack_trace('exception on shutdown', t, v, tb)
89 return True
90
91 #-------------------------------------------------------------------------
93
94 if t == OSError:
95 if not hasattr(t, 'winerror'):
96 return False
97 if getattr(t, 'winerror') != 126:
98 return False
99 else:
100 if t != ImportError:
101 return False
102
103 wx.EndBusyCursor()
104
105 _log.error('module [%s] not installed', v)
106 gmGuiHelpers.gm_show_error (
107 aTitle = _('Missing GNUmed module'),
108 aMessage = _(
109 'GNUmed detected that parts of it are not\n'
110 'properly installed. The following message\n'
111 'names the missing part:\n'
112 '\n'
113 ' "%s"\n'
114 '\n'
115 'Please make sure to get the missing\n'
116 'parts installed. Otherwise some of the\n'
117 'functionality will not be accessible.'
118 ) % v
119 )
120 return True
121
122 #-------------------------------------------------------------------------
124
125 if t != KeyboardInterrupt:
126 return False
127
128 print("<Ctrl-C>: Shutting down ...")
129 top_win = wx.GetApp().GetTopWindow()
130 wx.CallAfter(top_win.Close)
131 return True
132
133 #-------------------------------------------------------------------------
135
136 if t != gmExceptions.AccessDenied:
137 return False
138
139 _log.error('access permissions violation detected')
140 wx.EndBusyCursor()
141 gmLog2.flush()
142 txt = ' ' + v.errmsg
143 if v.source is not None:
144 txt += _('\n Source: %s') % v.source
145 if v.code is not None:
146 txt += _('\n Code: %s') % v.code
147 if v.details is not None:
148 txt += _('\n Details (first 250 characters):\n%s\n%s\n%s') % (
149 u_box_horiz_single * 50,
150 v.details[:250],
151 u_box_horiz_single * 50
152 )
153 gmGuiHelpers.gm_show_error (
154 aTitle = _('Access violation'),
155 aMessage = _(
156 'You do not have access to this part of GNUmed.\n'
157 '\n'
158 '%s'
159 ) % txt
160 )
161 return True
162
163 #-------------------------------------------------------------------------
165
166 if not gmPG2.exception_is_connection_loss(v):
167 return False
168
169 gmPG2.log_pg_exception_details(v)
170 gmLog2.log_stack_trace('lost connection', t, v, tb)
171 wx.EndBusyCursor()
172 gmLog2.flush()
173 gmGuiHelpers.gm_show_error (
174 aTitle = _('Lost connection'),
175 aMessage = _(
176 'Since you were last working in GNUmed,\n'
177 'your database connection timed out.\n'
178 '\n'
179 'This GNUmed session is now expired.\n'
180 '\n'
181 'You will have to close this client and\n'
182 'restart a new GNUmed session.'
183 )
184 )
185 return True
186
187 #-------------------------------------------------------------------------
189 if t != wx.wxAssertionError:
190 return False
191 _log.exception('a wxGTK assertion failed:')
192 _log.warning('continuing and hoping for the best')
193 return True
194
195 #-------------------------------------------------------------------------
197
198 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
199
200 if __handle_access_violation(t, v, tb):
201 return
202
203 if __handle_ctrl_c(t, v, tb):
204 return
205
206 if __handle_exceptions_on_shutdown(t, v, tb):
207 return
208
209 if __ignore_dead_objects_from_async(t, v, tb):
210 return
211
212 if __handle_import_error(t, v, tb):
213 return
214
215 # if __handle_wxgtk_assertion(t, v, tb)
216 # return
217
218 # other exceptions
219 _cfg = gmCfg2.gmCfgData()
220 if _cfg.get(option = 'debug') is False:
221 _log.error('enabling debug mode')
222 _cfg.set_option(option = 'debug', value = True)
223 root_logger = logging.getLogger()
224 root_logger.setLevel(logging.DEBUG)
225 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
226
227 if __handle_lost_db_connection(t, v, tb):
228 return
229
230 gmLog2.log_stack_trace(None, t, v, tb)
231
232 # only do this here or else we can invalidate the stack trace
233 # by Windows throwing an exception ... |-(
234 # careful: MSW does reference counting on Begin/End* :-(
235 wx.EndBusyCursor()
236
237 name = os.path.basename(_logfile_name)
238 name, ext = os.path.splitext(name)
239 new_name = os.path.expanduser(os.path.join (
240 '~',
241 '.gnumed',
242 'error_logs',
243 '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
244 ))
245
246 dlg = cUnhandledExceptionDlg(None, -1, exception = (t, v, tb), logfile = new_name)
247 dlg.ShowModal()
248 comment = dlg._TCTRL_comment.GetValue()
249 dlg.DestroyLater()
250 if (comment is not None) and (comment.strip() != ''):
251 _log.error('user comment: %s', comment.strip())
252
253 _log.warning('syncing log file for backup to [%s]', new_name)
254 gmLog2.flush()
255 # keep a copy around
256 shutil.copy2(_logfile_name, new_name)
257
258 #------------------------------------------------------------------------
260
261 global _logfile_name
262 _logfile_name = gmLog2._logfile_name
263
264 global _local_account
265 _local_account = os.path.basename(os.path.expanduser('~'))
266
267 set_helpdesk(gmPraxis.gmCurrentPraxisBranch().helpdesk)
268 set_staff_name(_local_account)
269 set_is_public_database(False)
270 set_sender_email(None)
271 set_client_version('gmExceptionHandlingWidgets.py <default>')
272
273 gmDispatcher.connect(signal = 'application_closing', receiver = _on_application_closing)
274
275 global APP_PID
276 APP_PID = os.getpid()
277 _log.debug('registered PID [%s] for aborting if necessary', APP_PID)
278
279 global _prev_excepthook
280 _prev_excepthook = sys.excepthook
281 sys.excepthook = handle_uncaught_exception_wx
282
283 return True
284
285 #------------------------------------------------------------------------
287 if _prev_excepthook is None:
288 sys.excepthook = sys.__excepthook__
289 return True
290 sys.excepthook = _prev_excepthook
291 return True
292
293 #------------------------------------------------------------------------
295 global application_is_closing
296 # used to ignore a few exceptions, such as when the
297 # C++ object has been destroyed before the Python one
298 application_is_closing = True
299
300 # ========================================================================
302
303 if (comment is None) or (comment.strip() == ''):
304 comment = wx.GetTextFromUser (
305 message = _(
306 'Please enter a short note on what you\n'
307 'were about to do in GNUmed:'
308 ),
309 caption = _('Sending bug report'),
310 parent = parent
311 )
312 if comment.strip() == '':
313 comment = '<user did not comment on bug report>'
314
315 receivers = []
316 if helpdesk is not None:
317 receivers = regex.findall (
318 '[\S]+@[\S]+',
319 helpdesk.strip(),
320 flags = regex.UNICODE
321 )
322 if len(receivers) == 0:
323 if _is_public_database:
324 receivers = ['gnumed-bugs@gnu.org']
325
326 receiver_string = wx.GetTextFromUser (
327 message = _(
328 'Edit the list of email addresses to send the\n'
329 'bug report to (separate addresses by spaces).\n'
330 '\n'
331 'Note that <gnumed-bugs@gnu.org> refers to\n'
332 'the public (!) GNUmed bugs mailing list.'
333 ),
334 caption = _('Sending bug report'),
335 default_value = ','.join(receivers),
336 parent = parent
337 )
338 if receiver_string.strip() == '':
339 return
340
341 receivers = regex.findall (
342 '[\S]+@[\S]+',
343 receiver_string,
344 flags = regex.UNICODE
345 )
346
347 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
348 parent,
349 -1,
350 caption = _('Sending bug report'),
351 question = _(
352 'Your bug report will be sent to:\n'
353 '\n'
354 '%s\n'
355 '\n'
356 'Make sure you have reviewed the log file for potentially\n'
357 'sensitive information before sending out the bug report.\n'
358 '\n'
359 'Note that emailing the report may take a while depending\n'
360 'on the speed of your internet connection.\n'
361 ) % '\n'.join(receivers),
362 button_defs = [
363 {'label': _('Send report'), 'tooltip': _('Yes, send the bug report.')},
364 {'label': _('Cancel'), 'tooltip': _('No, do not send the bug report.')}
365 ],
366 show_checkbox = True,
367 checkbox_msg = _('include log file in bug report')
368 )
369 dlg._CHBOX_dont_ask_again.SetValue(_is_public_database)
370 go_ahead = dlg.ShowModal()
371 if go_ahead == wx.ID_NO:
372 dlg.DestroyLater()
373 return
374
375 include_log = dlg._CHBOX_dont_ask_again.GetValue()
376 if not _is_public_database:
377 if include_log:
378 result = gmGuiHelpers.gm_show_question (
379 _(
380 'The database you are connected to is marked as\n'
381 '"in-production with controlled access".\n'
382 '\n'
383 'You indicated that you want to include the log\n'
384 'file in your bug report. While this is often\n'
385 'useful for debugging the log file might contain\n'
386 'bits of patient data which must not be sent out\n'
387 'without de-identification.\n'
388 '\n'
389 'Please confirm that you want to include the log !'
390 ),
391 _('Sending bug report')
392 )
393 include_log = (result is True)
394
395 if sender is None:
396 sender = _('<not supplied>')
397 else:
398 if sender.strip() == '':
399 sender = _('<not supplied>')
400
401 msg = """\
402 Report sent via GNUmed's handler for unexpected exceptions.
403
404 user comment : %s
405
406 client version: %s
407
408 system account: %s
409 staff member : %s
410 sender email : %s
411
412 # enable Launchpad bug tracking
413 affects gnumed
414 tag automatic-report
415 importance medium
416
417 """ % (comment, _client_version, _local_account, _staff_name, sender)
418 if include_log:
419 _log.error(comment)
420 _log.warning('syncing log file for emailing')
421 gmLog2.flush()
422 attachments = [ [_logfile_name, 'text/plain', 'quoted-printable'] ]
423 else:
424 attachments = None
425
426 dlg.DestroyLater()
427
428 wx.BeginBusyCursor()
429 _cfg = gmCfg2.gmCfgData()
430 try:
431 gmNetworkTools.compose_and_send_email (
432 sender = '%s <%s>' % (_staff_name, gmNetworkTools.default_mail_sender),
433 receiver = receivers,
434 subject = '<bug>: %s' % comment,
435 message = msg,
436 server = gmNetworkTools.default_mail_server,
437 auth = {'user': gmNetworkTools.default_mail_sender, 'password': 'gnumed-at-gmx-net'},
438 debug = _cfg.get(option = 'debug'),
439 attachments = attachments
440 )
441 gmDispatcher.send(signal='statustext', msg = _('Bug report has been emailed.'))
442 except Exception:
443 _log.exception('cannot send bug report')
444 gmDispatcher.send(signal='statustext', msg = _('Bug report COULD NOT be emailed.'))
445 finally:
446 wx.EndBusyCursor()
447
448 # ========================================================================
449 from Gnumed.wxGladeWidgets import wxgUnhandledExceptionDlg
450
452
454
455 exception = kwargs['exception']
456 del kwargs['exception']
457 self.logfile = kwargs['logfile']
458 del kwargs['logfile']
459
460 wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg.__init__(self, *args, **kwargs)
461
462 self.Title = '%s [PID %s]' % (self.Title, APP_PID)
463
464 if _sender_email is not None:
465 self._TCTRL_sender.SetValue(_sender_email)
466 self._TCTRL_helpdesk.SetValue(_helpdesk)
467 self._TCTRL_logfile.SetValue(self.logfile)
468 t, v, tb = exception
469 self._TCTRL_traceback.SetValue ('%s: %s\n%s: %s\n%s' % (
470 'type', t,
471 'value', v,
472 ''.join(traceback.format_tb(tb))
473 ))
474 self.Fit()
475
476 #------------------------------------------
490
491 #------------------------------------------
499 # ideas:
500 # - start timer thread with abort code from [close] button
501 # - start a detached shell script at the OS level with "sleep 20 ; kill -15 PID ; kill -9 PID"
502
503 #------------------------------------------
512
513 #------------------------------------------
519
520 # ========================================================================
521
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Feb 29 02:55:27 2020 | http://epydoc.sourceforge.net |