~ubuntu-branches/debian/wheezy/gtg/wheezy

« back to all changes in this revision

Viewing changes to GTG/gtk/crashhandler.py

  • Committer: Package Import Robot
  • Author(s): Luca Falavigna
  • Date: 2012-04-10 23:08:21 UTC
  • mfrom: (1.1.6) (2.2.14 sid)
  • Revision ID: package-import@ubuntu.com-20120410230821-85vh6srag5gcz9fi
Tags: 0.2.9-1
* New upstream release (Closes: #668096).
  - Implement a search text box (Closes: #650279).
  - Window title reflects active tasks (LP: #537096).
  - Fix misbehaviours of the indicator applet (LP: #548836, #676353).
  - Fix crash when selecting notification area plugin twice (LP: #550321).
  - Fix sorting of tasks by date (LP: #556159).
  - Fix excessive delays at startup (LP: #558600).
  - Fix crash with dates having unknown values (LP: #561449).
  - Fix crash issued when pressing delete key (LP: #583103).
  - Keep notification plugin enabled after logoff (LP: #617257).
  - Fix Hamster plugin to work with recent Hamster versions (LP: #620313).
  - No longer use non-unicode strings (LP: #680632).
  - New RTM sync mechanism (LP: #753327).
  - Fix crashes while handling XML storage file (LP: #916474, #917634).
* debian/patches/*:
  - Drop all patches, they have been merged upstream.
* debian/patches/shebang.patch:
  - Fix shebang line.
* debian/patches/manpages.patch:
  - Fix some groff warnings in gtg_new_task man page
* debian/compat:
  - Bump compatibility level to 9.
* debian/control:
  - Bump X-Python-Version to >= 2.6.
  - Add python-liblarch and python-liblarch-gtk to Depends field.
  - Add python-cheetah, python-geoclue, python-gnomekeyring,
    python-launchpadlib and python-suds to Suggests field.
  - Bump Standards-Version to 3.9.3.
* debian/copyright:
  - Refresh copyright information.
  - Format now points to copyright-format site.
* debian/rules:
  - Make gtcli_bash_completion script executable.
* debian/watch:
  - Update watch file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# Copyright 2010 David D. Lowe
 
3
# All rights reserved.
 
4
#
 
5
# Permission is hereby granted, free of charge, to any person obtaining a copy
 
6
# of this software and associated documentation files (the "Software"), to deal
 
7
# in the Software without restriction, including without limitation the rights
 
8
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 
9
# copies of the Software, and to permit persons to whom the Software is
 
10
# furnished to do so, subject to the following conditions:
 
11
#
 
12
# The above copyright notice and this permission notice shall be included in
 
13
# all copies or substantial portions of the Software.
 
14
#
 
15
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 
16
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 
17
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 
18
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 
19
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 
20
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 
21
# THE SOFTWARE.
 
22
#
 
23
 
 
24
"""GTK except hook for your applications.
 
25
To use, simply import this module and call gtkcrashhandler.initialize().
 
26
Import this module before calling gtk.main().
 
27
 
 
28
If gtkcrashhandler cannot import gtk, pygtk, pango or gobject,
 
29
gtkcrashhandler will print a warning and use the default excepthook.
 
30
 
 
31
If you're using multiple threads, use gtkcrashhandler_thread decorator."""
 
32
 
 
33
import sys
 
34
import os
 
35
import time
 
36
import signal
 
37
from contextlib import contextmanager
 
38
 
 
39
from GTG import info
 
40
 
 
41
 
 
42
try:
 
43
    import pygtk
 
44
    pygtk.require("2.0") # not tested on earlier versions
 
45
    import gtk
 
46
    import pango
 
47
    import gobject
 
48
    _gtk_initialized = True
 
49
except Exception:
 
50
    print >> sys.stderr, "gtkcrashhandler could not load GTK 2.0"
 
51
    _gtk_initialized = False
 
52
import traceback
 
53
from gettext import gettext as _
 
54
import threading
 
55
 
 
56
APP_NAME = None
 
57
MESSAGE = _("We're terribly sorry. Could you help us fix the problem by " \
 
58
          "reporting the crash?")
 
59
USE_APPORT = False
 
60
 
 
61
_old_sys_excepthook = None # None means that initialize() has not been called
 
62
                           # yet.
 
63
 
 
64
dialog = None
 
65
 
 
66
def initialize(app_name=None, message=None, use_apport=False):
 
67
    """Initialize the except hook built on GTK.
 
68
 
 
69
    Keyword arguments:
 
70
    app_name   -- The current application's name to be read by humans,
 
71
                  untranslated.
 
72
    message    -- A message that will be displayed in the error dialog,
 
73
                  replacing the default message string. Untranslated.
 
74
                  If you don't want a message, pass "".
 
75
    use_apport -- If set to True, gtkcrashhandler will override the settings
 
76
                  in /etc/default/apport and call apport if possible,
 
77
                  silently failing if not.
 
78
                  If set to False, the normal behaviour will be executed,
 
79
                  which may mean Apport kicking in anyway.
 
80
 
 
81
    """
 
82
    global APP_NAME, MESSAGE, USE_APPORT, _gtk_initialized, _old_sys_excepthook
 
83
    if app_name:
 
84
        APP_NAME = _(app_name)
 
85
    if not message is None:
 
86
        MESSAGE = _(message)
 
87
    if use_apport:
 
88
        USE_APPORT = use_apport
 
89
    if _gtk_initialized == True and _old_sys_excepthook is None:
 
90
        # save sys.excepthook first, as it may not be sys.__excepthook__
 
91
        # (for example, it might be Apport's python hook)
 
92
        _old_sys_excepthook = sys.excepthook
 
93
        # replace sys.excepthook with our own
 
94
        sys.excepthook = _replacement_excepthook
 
95
 
 
96
 
 
97
def _replacement_excepthook(type, value, tracebk, thread=None):
 
98
    """This function will replace sys.excepthook."""
 
99
    # create traceback string and print it
 
100
    tb = "".join(traceback.format_exception(type, value, tracebk))
 
101
    if thread:
 
102
        if not isinstance(thread, threading._MainThread):
 
103
            tb = "Exception in thread %s:\n%s" % (thread.getName(), tb)
 
104
    print >> sys.stderr, tb
 
105
 
 
106
    # determine whether to add a "Report problem..." button
 
107
    add_apport_button = False
 
108
    global USE_APPORT
 
109
    if USE_APPORT:
 
110
        # see if this file is from a properly installed distribution package
 
111
        try:
 
112
            from apport.fileutils import likely_packaged
 
113
            try:
 
114
                filename = os.path.realpath(os.path.join(os.getcwdu(),
 
115
                    sys.argv[0]))
 
116
            except:
 
117
                filename = os.path.realpath("/proc/%i/exe" % os.getpid())
 
118
            if not os.path.isfile(filename) or not os.access(filename, os.X_OK):
 
119
                raise Exception()
 
120
            add_apport_button = likely_packaged(filename)
 
121
        except:
 
122
            add_apport_button = False
 
123
 
 
124
    res = show_error_window(tb, add_apport_button=add_apport_button)
 
125
 
 
126
    if res == 3: # report button clicked
 
127
        # enable apport, overriding preferences
 
128
        try:
 
129
            # create new temporary configuration file, where enabled=1
 
130
            import re
 
131
            from apport.packaging_impl import impl as apport_packaging
 
132
            newconfiguration = "# temporary apport configuration file " \
 
133
                               "by gtkcrashhandler.py\n\n"
 
134
            try:
 
135
                for line in open(apport_packaging.configuration):
 
136
                    if re.search('^\s*enabled\s*=\s*0\s*$', line) is None:
 
137
                        newconfiguration += line
 
138
            finally:
 
139
                newconfiguration += "enabled=1"
 
140
            import tempfile
 
141
            tempfile, tempfilename = tempfile.mkstemp()
 
142
            os.write(tempfile, newconfiguration)
 
143
            os.close(tempfile)
 
144
 
 
145
            # set apport to use this configuration file, temporarily
 
146
            apport_packaging.configuration = tempfilename
 
147
            # override Apport's ignore settings for this app
 
148
            from apport.report import Report
 
149
            Report.check_ignored = lambda self: False
 
150
        except:
 
151
            pass
 
152
 
 
153
    if res in (2, 3): # quit
 
154
        sys.stderr = os.tmpfile()
 
155
        global _old_sys_excepthook
 
156
        _old_sys_excepthook(type, value, tracebk)
 
157
        sys.stderr = sys.__stderr__
 
158
        os._exit(1)
 
159
 
 
160
def show_error_window(error_string, add_apport_button=False):
 
161
    """Displays an error dialog, and returns the response ID.
 
162
 
 
163
    error_string       -- the error's output (usually a traceback)
 
164
    add_apport_button  -- whether to add a 'report with apport' button
 
165
 
 
166
    Returns the response ID of the dialog, 1 for ignore, 2 for close and
 
167
    3 for apport.
 
168
    """
 
169
    # initialize dialog
 
170
    title = _("An error has occurred")
 
171
    global APP_NAME
 
172
    if APP_NAME:
 
173
        title = APP_NAME
 
174
    global dialog
 
175
    # Do not allow more than one error window
 
176
    if dialog is not None:
 
177
        return 1
 
178
 
 
179
    dialog = gtk.Dialog(title)
 
180
 
 
181
 
 
182
    # title Label
 
183
    label = gtk.Label()
 
184
    label.set_markup("<b>" + _("It looks like an error has occurred.") + "</b>")
 
185
    label.set_alignment(0, 0.5)
 
186
    dialog.get_content_area().pack_start(label, False)
 
187
 
 
188
    # message Label
 
189
    global MESSAGE
 
190
    text_label = gtk.Label()
 
191
    text_label.set_markup(MESSAGE)
 
192
    text_label.set_alignment(0, 0.5)
 
193
    text_label.set_line_wrap(True)
 
194
    def text_label_size_allocate(widget, rect):
 
195
        """Lets label resize correctly while wrapping text."""
 
196
        widget.set_size_request(rect.width, -1)
 
197
    text_label.connect("size-allocate", text_label_size_allocate)
 
198
    if not MESSAGE == "":
 
199
        dialog.get_content_area().pack_start(text_label, False)
 
200
 
 
201
    # TextView with error_string
 
202
    buffer = gtk.TextBuffer()
 
203
    buffer.set_text(error_string)
 
204
    textview = gtk.TextView()
 
205
    textview.set_buffer(buffer)
 
206
    textview.set_editable(False)
 
207
    try:
 
208
        textview.modify_font(pango.FontDescription("monospace 8"))
 
209
    except Exception:
 
210
        print >> sys.stderr, "gtkcrashhandler: modify_font raised an exception"
 
211
 
 
212
    # allow scrolling of textview
 
213
    scrolled = gtk.ScrolledWindow()
 
214
    scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
215
    scrolled.add_with_viewport(textview)
 
216
 
 
217
    # hide the textview in an Expander widget
 
218
    expander = gtk.expander_new_with_mnemonic(_("_Details"))
 
219
    expander.add(scrolled)
 
220
    expander.connect('activate', on_expanded)
 
221
    dialog.get_content_area().pack_start(expander, True)
 
222
 
 
223
    # add buttons
 
224
    if add_apport_button:
 
225
        dialog.add_button(_("_Report this problem..."), 3)
 
226
    # If we're have multiple threads, or if we're in a GTK callback,
 
227
    # execution can continue normally in other threads, so add button
 
228
    if gtk.main_level() > 0 or threading.activeCount() > 1:
 
229
        dialog.add_button(_("_Ignore the error"), 1)
 
230
    dialog.add_button(("_Close the program"), 2)
 
231
    dialog.set_default_response(2)
 
232
 
 
233
    # set dialog aesthetic preferences
 
234
    dialog.set_border_width(12)
 
235
    dialog.get_content_area().set_spacing(4)
 
236
    dialog.set_resizable(False)
 
237
 
 
238
    # show the dialog and act on it
 
239
    dialog.show_all()
 
240
    res = dialog.run()
 
241
    dialog.destroy()
 
242
    if res < 0:
 
243
        res = 2
 
244
    return res
 
245
 
 
246
def on_expanded(widget):
 
247
    global dialog
 
248
    dialog.set_size_request(600, 600)
 
249
 
 
250
 
 
251
def gtkcrashhandler_thread(run):
 
252
    """gtkcrashhandler_thread is a decorator for the run() method of
 
253
    threading.Thread.
 
254
 
 
255
    If you forget to use this decorator, exceptions in threads will be
 
256
    printed to standard error output, and GTK's main loop will continue to run.
 
257
 
 
258
    #Example 1:
 
259
    class ExampleThread(threading.Thread):
 
260
        @gtkcrashhandler_thread
 
261
        def run(self):
 
262
            1 / 0 # this error will be caught by gtkcrashhandler
 
263
 
 
264
    #Example 2:
 
265
    def function(arg):
 
266
        arg / 0 # this error will be caught by gtkcrashhandler
 
267
    threading.Thread(target=gtkcrashhandler_thread(function), args=(1,)).start()
 
268
    """
 
269
    def gtkcrashhandler_wrapped_run(*args, **kwargs):
 
270
        try:
 
271
            run(*args, **kwargs)
 
272
        except Exception, ee:
 
273
            lock = threading.Lock()
 
274
            lock.acquire()
 
275
            tb = sys.exc_info()[2]
 
276
            if gtk.main_level() > 0:
 
277
                gobject.idle_add(
 
278
                    lambda ee=ee, tb=tb, thread=threading.currentThread():
 
279
                    _replacement_excepthook(ee.__class__, ee, tb, thread=thread))
 
280
            else:
 
281
                time.sleep(0.1) # ugly hack, seems like threads that are
 
282
                                # started before running gtk.main() cause
 
283
                                # this one to crash.
 
284
                                # This delay allows gtk.main() to initialize
 
285
                                # properly.
 
286
                                # My advice: run gtk.main() before starting
 
287
                                # any threads or don't run gtk.main() at all
 
288
                _replacement_excepthook(ee.__class__, ee, tb,
 
289
                                        thread=threading.currentThread())
 
290
            lock.release()
 
291
    # return wrapped run if gtkcrashhandler has been initialized
 
292
    global _gtk_initialized, _old_sys_excepthook
 
293
    if _gtk_initialized and _old_sys_excepthook:
 
294
        return gtkcrashhandler_wrapped_run
 
295
    else:
 
296
        return run
 
297
 
 
298
if __name__ == "__main__":
 
299
    # throw test exception
 
300
    initialize(app_name="gtkcrashhandler", message="Don't worry, though. This "
 
301
        "is just a test. To use the code properly, call "
 
302
        "gtkcrashhandler.initialize() in your PyGTK app to automatically catch "
 
303
        " any Python exceptions like this.")
 
304
    class DoNotRunException(Exception):
 
305
        def __str__(self):
 
306
            return "gtkcrashhandler.py should imported, not run"
 
307
    raise DoNotRunException()
 
308
 
 
309
 
 
310
## We handle initialization directly here, since this module will be used as a
 
311
#  singleton
 
312
        #we listen for signals from the system in order to save our configuration
 
313
        # if GTG is forcefully terminated (e.g.: on shutdown).
 
314
@contextmanager
 
315
def signal_catcher(callback):
 
316
    #if TERM or ABORT are caught, we execute the callback function
 
317
    for s in [signal.SIGABRT, signal.SIGTERM]:
 
318
        signal.signal(s, lambda a, b: callback())
 
319
    yield
 
320
 
 
321
initialize(app_name = "Getting Things GNOME!",
 
322
           message  =  "GTG" + info.VERSION + 
 
323
           _(" has crashed. Please report the bug on <a "\
 
324
             "href=\"http://bugs.edge.launchpad.net/gtg\">our Launchpad page</a>."\
 
325
             " If you have Apport installed, it will be started for you."),       \
 
326
          use_apport = True)