Title: [commits] (heikki) [11374] Bug 4025, display an 'internal error' window to users if
Revision
11374
Author
heikki
Date
2006-08-10 10:56:35 -0700 (Thu, 10 Aug 2006)

Log Message

Bug 4025, display an 'internal error' window to users if
something gets written to stdout or stderr, for example
when an exception happens. The user can submit the information
to OSAF from the error window. Use --stderr to prevent that
window. r=robin.

Modified Paths

Added Paths

Diff

Modified: trunk/chandler/Chandler.py (11373 => 11374)

--- trunk/chandler/Chandler.py	2006-08-10 16:32:38 UTC (rev 11373)
+++ trunk/chandler/Chandler.py	2006-08-10 17:56:35 UTC (rev 11374)
@@ -21,7 +21,6 @@
 import application.Globals as Globals
 import application.Utility as Utility
 
-
 def main():
 
     # Process any command line switches and any environment variable values
@@ -30,6 +29,9 @@
     def realMain():
         
         Utility.initProfileDir(Globals.options)
+
+        from application import feedback
+        feedback.initRuntimeLog(Globals.options.profileDir)
         
         Globals.chandlerDirectory = Utility.locateChandlerDirectory()
     
@@ -65,7 +67,7 @@
         # useBestVisual - uses best screen resolutions on some old computers.
         #                 See wxApp.SetUseBestVisual
 
-        redirect = __debug__ and not Globals.options.stderr
+        redirect = not Globals.options.stderr
         app = wxApplication(redirect=redirect, useBestVisual=True)
 
         app.MainLoop()
@@ -108,20 +110,38 @@
 
             logging.error(longMessage)
 
-            if wx.GetApp() is None:
+            if getattr(globals(), 'app', None) is None or wx.GetApp() is None:
                 app = wx.PySimpleApp()
+                app.ignoreSynchronizeWidget = True
 
             try:
-                from application.dialogs.UncaughtExceptionDialog import ErrorDialog
-                dialog = ErrorDialog (longMessage)
+                # Let's try the best (and most complicated) option
+                # first
+                # See if we already have a window up, and if so, reuse it
+                from application import feedback
+                feedback.destroyAppOnClose = True
+                win = feedback.activeWindow
+                if win is None:
+                    win = feedback.FeedbackWindow()
+                    win.CreateOutputWindow('')
+                for line in backtrace:
+                    win.write(line)
+                if not app.IsMainLoopRunning():
+                    app.MainLoop()
             except:
-                frames = 8
-                line2 = u"Here are the bottom %(frames)s frames of the stack:\n" % {'frames': frames - 1}
-                shortMessage = "".join([line1, line2, "\n"] + backtrace[-frames:])
-                dialog = wx.MessageDialog(None, shortMessage, "Chandler", 
-                                          wx.OK | wx.ICON_INFORMATION)
-            dialog.ShowModal()
-            dialog.Destroy()
+                # Fall back to our custom (but simple) error dialog
+                try:
+                    from application.dialogs.UncaughtExceptionDialog import ErrorDialog
+                    dialog = ErrorDialog(longMessage)
+                except:
+                    # Fall back to MessageDialog
+                    frames = 8
+                    line2 = u"Here are the bottom %(frames)s frames of the stack:\n" % {'frames': frames - 1}
+                    shortMessage = "".join([line1, line2, "\n"] + backtrace[-frames:])
+                    dialog = wx.MessageDialog(None, shortMessage, "Chandler", 
+                                              wx.OK | wx.ICON_INFORMATION)
+                dialog.ShowModal()
+                dialog.Destroy()
 
 
     #@@@Temporary testing tool written by Morgen -- DJA

Modified: trunk/chandler/application/Application.py (11373 => 11374)

--- trunk/chandler/application/Application.py	2006-08-10 16:32:38 UTC (rev 11373)
+++ trunk/chandler/application/Application.py	2006-08-10 17:56:35 UTC (rev 11374)
@@ -18,7 +18,7 @@
 
 from new import classobj
 from i18n import OSAFMessageFactory as _, getImage
-import schema
+import schema, feedback
 from version import version
 
 from repository.persistence.RepositoryError import \
@@ -243,6 +243,8 @@
         displayInfoWhileProcessing (_("Checkpointing repository..."),
                                     app.UIRepositoryView.repository.checkpoint)
 
+        feedback.stopRuntimeLog(Globals.options.profileDir)
+
         # When we quit, as each wxWidget window is torn down our handlers that
         # track changes to the selection are called, and we don't want to count
         # these changes, since they weren't caused by user actions.
@@ -253,6 +255,8 @@
 
 class wxApplication (wx.App):
 
+    outputWindowClass = feedback.FeedbackWindow
+
     __CHANDLER_STARTED_UP = False # workaround for bug 4362
 
     def OnInit(self):

Added: trunk/chandler/application/feedback.py (11373 => 11374)

--- trunk/chandler/application/feedback.py	2006-08-10 16:32:38 UTC (rev 11373)
+++ trunk/chandler/application/feedback.py	2006-08-10 17:56:35 UTC (rev 11374)
@@ -0,0 +1,311 @@
+#   Copyright (c) 2006 Open Source Applications Foundation
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+import os, sys, time, codecs
+from cgi import escape
+import wx
+from i18n import OSAFMessageFactory as _
+import Globals
+import version
+from feedback_xrc import *
+
+
+activeWindow = None
+destroyAppOnClose = False
+
+
+def initRuntimeLog(profileDir):
+    """
+    Append the current time as application start time to run time log,
+    creating the log file if necessary.
+    """
+    try:
+        f = open(os.path.join(profileDir, 'start.log'), 'a+')
+        f.write('start:%s\n' % long(time.time()))
+        f.close()
+    except:
+        pass
+
+
+def stopRuntimeLog(profileDir):
+    """
+    Append the current time as application stop time.
+    """
+    try:
+        logfile = os.path.join(profileDir, 'start.log') 
+        f = open(logfile, 'a+')
+        f.write('stop:%s\n' % long(time.time()))
+        f.close()
+        return logfile
+    except:
+        pass
+
+
+class FeedbackWindow(wx.PyOnDemandOutputWindow):
+    """
+    An error dialog that would be shown in case there is an uncaught
+    exception. The user can send the error report back to us as well.
+    """
+    def _fillOptionalSection(self):
+        try:    
+            # columns
+            self.frame.sysInfo.InsertColumn(0, 'key')
+            self.frame.sysInfo.InsertColumn(1, 'value')
+
+            # data
+            self.frame.sysInfo.InsertStringItem(0, 'os.getcwd')
+            self.frame.sysInfo.SetStringItem(0, 1, '%s' % os.getcwd())
+            index = 1
+            for argv in sys.argv:
+                self.frame.sysInfo.InsertStringItem(index, 'sys.argv')
+                self.frame.sysInfo.SetStringItem(index, 1, '%s' % argv)
+                index += 1
+            for path in sys.path:
+                self.frame.sysInfo.InsertStringItem(index, 'sys.path')
+                self.frame.sysInfo.SetStringItem(index, 1, '%s' % path)
+                index += 1
+            for key in os.environ.keys():
+                self.frame.sysInfo.InsertStringItem(index, 'os.environ')
+                self.frame.sysInfo.SetStringItem(index, 1, '%s: %s' % (key, os.environ[key]))
+                index += 1
+            try:
+                f = codecs.open(os.path.join(Globals.options.profileDir,
+                                             'chandler.log'),
+                                encoding='utf-8', mode='r', errors='ignore')
+                for line in f.readlines()[-20:]:
+                    self.frame.sysInfo.InsertStringItem(index, 'chandler.log')
+                    self.frame.sysInfo.SetStringItem(index, 1, '%s' % line)
+                    index += 1
+            except:
+                pass
+            try:
+                f = codecs.open(os.path.join(Globals.options.profileDir,
+                                             'twisted.log'),
+                                encoding='utf-8', mode='r', errors='ignore')
+                for line in f.readlines()[-20:]:
+                    self.frame.sysInfo.InsertStringItem(index, 'twisted.log')
+                    self.frame.sysInfo.SetStringItem(index, 1, '%s' % line)
+                    index += 1
+            except:
+                pass
+
+            self.frame.sysInfo.SetColumnWidth(0, wx.LIST_AUTOSIZE)
+            self.frame.sysInfo.SetColumnWidth(1, wx.LIST_AUTOSIZE)
+        except:
+            pass
+
+        self.frame.delButton.Bind(wx.EVT_BUTTON, self.OnDelItem, self.frame.delButton)
+        self.frame.sysInfo.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+
+        self.frame.sysInfo.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
+        self.frame.sysInfo.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnItemDeselected)
+
+    def OnItemSelected(self, event):
+        self.frame.delButton.Enable()
+
+    def OnItemDeselected(self, event):
+        if self.frame.sysInfo.GetSelectedItemCount() < 1:
+            self.frame.delButton.Disable()
+            # Disabling the focused button disables keyboard navigation
+            # unless we set the focus to something else - let's put it
+            # on the list control.
+            self.frame.sysInfo.SetFocus()
+
+    def OnKeyDown(self, event):
+        if event.GetKeyCode() == wx.WXK_DELETE:
+            return self.OnDelItem(event)
+        event.Skip()
+
+    def OnDelItem(self, event):
+        while True:
+            index = self.frame.sysInfo.GetNextItem(-1, state=wx.LIST_STATE_SELECTED)
+            if index < 0:
+                break
+            self.frame.sysInfo.DeleteItem(index)
+
+    def _fillRequiredSection(self, st):
+        # Time since last failure
+        try:
+            logfile = stopRuntimeLog(Globals.options.profileDir)
+            try:
+                timeSinceLastError = 0
+                start = 0
+                for line in open(logfile):
+                    verb, value = line.split(':')
+                    # We may have corrupted start, start; stop, stop entries but that is ok,
+                    # we only count the time between start, stop pairs.
+                    if verb == 'start':
+                        start = long(value)
+                    elif start != 0:
+                        stop = long(value)
+                        if stop > start: # Skip over cases where we know system clock has changed
+                            timeSinceLastError += stop - start
+                        start = 0
+            except:
+                timeSinceLastError = 0
+            
+            self.frame.text.AppendText('Seconds since last error: %d\n' % timeSinceLastError)
+            
+            # Clear out the logfile
+            f = open(logfile, 'w')
+            f.close()
+        except:
+            pass
+        
+        # Version and other miscellaneous information
+        try:
+            self.frame.text.AppendText('Chandler Version: %s\n' % version.version)
+            
+            self.frame.text.AppendText('OS: %s\n' % os.name)
+            self.frame.text.AppendText('Platform: %s\n' % sys.platform)
+            try:
+                self.frame.text.AppendText('Windows Version: %s\n' % str(sys.getwindowsversion()))
+            except:
+                pass
+            self.frame.text.AppendText('Python Version: %s\n' % sys.version)
+        except:
+            pass
+        
+        # Traceback (actually just the first line of it)
+        self.frame.text.AppendText(st)
+
+    def CreateOutputWindow(self, st):
+        global activeWindow
+        activeWindow = self
+        
+        self.frame = xrcFRAME(None)
+        self.text = self.frame.text # superclass expects self.text
+        
+        self._fillRequiredSection(st)
+        self._fillOptionalSection()
+        self.frame.delButton.Disable()
+        
+        # Need accelerators so that we can make ESC close the window
+        accelerators = wx.AcceleratorTable([(wx.ACCEL_NORMAL, wx.WXK_ESCAPE,
+                                             wx.ID_CANCEL)
+                                           ])
+        self.frame.SetAcceleratorTable(accelerators)
+
+        size = wx.Size(450, 400)
+        self.frame.SetMinSize(size)
+        self.frame.Fit()
+        self.frame.Show(True)
+        
+        self.frame.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
+        self.frame.Bind(wx.EVT_BUTTON, self.OnCloseWindow, self.frame.closeButton)
+        self.frame.Bind(wx.EVT_MENU, self.OnCloseWindow)
+        self.frame.Bind(wx.EVT_BUTTON, self.OnSend, self.frame.sendButton)
+
+    def OnCloseWindow(self, event):
+        global activeWindow
+        wx.PyOnDemandOutputWindow.OnCloseWindow(self, event)
+        activeWindow = None
+        if destroyAppOnClose:
+            import sys
+            sys.exit(0)
+            # XXX This would probably be better (we might leak resources with
+            # XXX sys.exit), but causes Python crash            
+            #wx.GetApp().Destroy()
+
+    def OnSend(self, event):
+        self.frame.sendButton.Disable()
+        # Disabling the focused button disables keyboard navigation
+        # unless we set the focus to something else - let's put it
+        # on close button
+        self.frame.closeButton.SetFocus() 
+        self.frame.sendButton.SetLabel(_(u'Sending...'))
+        
+        try:
+            from M2Crypto import httpslib, SSL
+            # Try to load the CA certificates for secure SSL.
+            # If we can't load them, the data is hidden from casual observation,
+            # but a man-in-the-middle attack is possible.
+            ctx = SSL.Context()
+            opts = {}
+            if ctx.load_verify_locations('parcels/osaf/framework/certstore/cacert.pem') == 1:
+                ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+                opts['ssl_context'] = ctx
+            c = httpslib.HTTPSConnection('feedback.osafoundation.org', 443, opts)
+            body = buildXML(self.frame.comments, self.frame.email,
+                            self.frame.sysInfo, self.frame.text)
+            c.request('POST', '/desktop/post/submit', body)
+            response = c.getresponse()
+            if response.status != 200:
+                raise Exception('response.status=' + response.status)
+            c.close()
+        except:
+            self.frame.sendButton.SetLabel(_(u'Failed to send'))
+        else:
+            self.frame.sendButton.SetLabel(_(u'Sent'))
+        
+
+def buildXML(comments, email, optional, required):
+    """
+    Given the possible fields in the error dialog, build an XML file
+    of the data.
+    """
+    ret = ['<feedback xmlns="http://osafoundation.org/xmlns/feedback" version="0.1">']
+    
+    # The required field consists of field: value lines, followed by either
+    # traceback or arbitrary output that was printed to stdout or stderr.
+    lastElem = ''
+    for line in required.GetValue().split('\n'):
+        if lastElem == '':
+            sep = line.find(':')
+            if line.startswith('Traceback'):
+                lastElem = 'traceback'
+                ret.append('<%s>' % lastElem)
+                ret.append(escape(line))
+            elif sep < 0:
+                lastElem = 'output'
+                ret.append('<%s>' % lastElem)
+                ret.append(escape(line))
+            else:
+                field = line[:sep].replace(' ', '-')
+                value = line[sep + 1:].strip()
+                ret.append('<%s>%s</%s>' % (field, escape(value), field))
+        else:
+            ret.append(escape(line))
+    if lastElem != '':
+        ret.append('</%s>' % lastElem)
+    
+    # Optional email
+    ret.append('<email>%s</email>' % escape(email.GetValue()))
+    
+    # Optional comments
+    ret.append('<comments>')
+    ret.append(escape(comments.GetValue()))
+    ret.append('</comments>')
+    
+    # Optional system information and logs
+    for i in range(optional.GetItemCount()):
+        field = optional.GetItem(i, 0).GetText()
+        value = optional.GetItem(i, 1).GetText()
+        ret.append('<%s>%s</%s>' % (field, escape(value), field))
+
+    ret.append('</feedback>')
+    
+    s = '\n'.join(ret)
+    
+    if isinstance(s, unicode):
+        s = s.encode('utf8')
+    
+    # For debugging purposes:
+    #f = open('feedback.xml', 'w')
+    #f.write(s)
+    #f.close()
+
+    return s
+

Added: trunk/chandler/application/feedback.xrc (11373 => 11374)

--- trunk/chandler/application/feedback.xrc	2006-08-10 16:32:38 UTC (rev 11373)
+++ trunk/chandler/application/feedback.xrc	2006-08-10 17:56:35 UTC (rev 11374)
@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resource>
+  <object class="wxFrame" name="FRAME">
+    <title>Chandler: Internal Program Error</title>
+    <object class="wxPanel" name="panel">
+      <object class="wxBoxSizer">
+        <orient>wxVERTICAL</orient>
+        <object class="sizeritem">
+          <object class="wxStaticText">
+            <label>Chandler has experienced an internal error.  It is recommended that you restart 
+Chandler to avoid data corruption. 
+
+You may send information to OSAF about this error in order to assist with improving 
+the product.  Simply click the Send button below after optionally adding additional 
+information about how to reproduce the error.</label>
+          </object>
+          <flag>wxALL</flag>
+          <border>10</border>
+        </object>
+        <object class="sizeritem">
+          <object class="wxNotebook">
+            <object class="notebookpage">
+              <label>Required</label>
+              <object class="wxPanel" name="requiredPage">
+                <object class="wxBoxSizer">
+                  <orient>wxVERTICAL</orient>
+                  <object class="sizeritem">
+                    <object class="wxTextCtrl" name="text">
+                      <style>wxTE_MULTILINE|wxTE_READONLY</style>
+                    </object>
+                    <option>1</option>
+                    <flag>wxALL|wxEXPAND</flag>
+                    <border>5</border>
+                  </object>
+                </object>
+              </object>
+            </object>
+            <object class="notebookpage">
+              <label>Optional</label>
+              <object class="wxPanel" name="optionalPage">
+                <object class="wxBoxSizer">
+                  <orient>wxVERTICAL</orient>
+                  <object class="sizeritem">
+                    <object class="wxFlexGridSizer">
+                      <cols>2</cols>
+                      <vgap>5</vgap>
+                      <hgap>5</hgap>
+                      <object class="sizeritem">
+                        <object class="wxStaticText" name="">
+                          <label>&amp;Email:</label>
+                        </object>
+                        <flag>wxALIGN_RIGHT|wxALIGN_CENTRE_VERTICAL</flag>
+                      </object>
+                      <object class="sizeritem">
+                        <object class="wxTextCtrl" name="email"/>
+                        <flag>wxEXPAND</flag>
+                      </object>
+                      <object class="sizeritem">
+                        <object class="wxStaticText">
+                          <label>&amp;Comments: </label>
+                          <style></style>
+                        </object>
+                        <flag>wxALIGN_RIGHT</flag>
+                      </object>
+                      <object class="sizeritem">
+                        <object class="wxTextCtrl" name="comments">
+                          <style>wxTE_MULTILINE</style>
+                        </object>
+                        <flag>wxEXPAND</flag>
+                        <minsize>-1, 80</minsize>
+                      </object>
+                      <object class="sizeritem">
+
+                <object class="wxBoxSizer">
+                  <orient>wxVERTICAL</orient>
+
+                      <object class="sizeritem">
+                        <object class="wxStaticText">
+                          <label>System
+&amp;Information:</label>
+                        </object>
+                        <flag>wxALIGN_RIGHT</flag>
+                      </object>
+
+                      <object class="spacer">
+                        <size>0,0</size>
+                      </object>
+                      <object class="sizeritem">
+                        <object class="wxButton" name="delButton">
+                          <tooltip>Delete selected item</tooltip>
+                          <label>&amp;Delete</label>
+                        </object>
+                      </object>
+                </object>
+				
+                      </object>
+                      <object class="sizeritem">
+                        <object class="wxListCtrl" name="sysInfo">
+                          <style>wxLC_REPORT|wxLC_NO_HEADER</style>
+                        </object>
+                        <option>1</option>
+                        <flag>wxEXPAND</flag>
+                        <minsize>-1,80</minsize>
+                      </object>
+                      <growablecols>1</growablecols>
+                      <growablerows>2</growablerows>
+                    </object>
+                    <option>1</option>
+                    <flag>wxALL|wxEXPAND</flag>
+                    <border>5</border>
+                  </object>
+                </object>
+              </object>
+            </object>
+          </object>
+          <option>1</option>
+          <flag>wxALL|wxEXPAND</flag>
+          <border>10</border>
+        </object>
+        <object class="sizeritem">
+          <object class="wxBoxSizer">
+            <orient>wxHORIZONTAL</orient>
+            <object class="spacer">
+              <size>0,0</size>
+              <option>1</option>
+            </object>
+            <object class="sizeritem">
+              <object class="wxButton" name="sendButton">
+                <label>&amp;Send this feedback</label>
+              </object>
+              <flag>wxRIGHT</flag>
+              <border>8</border>
+            </object>
+            <object class="sizeritem">
+              <object class="wxButton" name="closeButton">
+                <label>&amp;Close</label>
+              </object>
+            </object>
+          </object>
+          <flag>wxBOTTOM|wxLEFT|wxRIGHT|wxEXPAND</flag>
+          <border>10</border>
+        </object>
+      </object>
+    </object>
+  </object>
+</resource>
\ No newline at end of file

Added: trunk/chandler/application/feedback_xrc.py (11373 => 11374)

--- trunk/chandler/application/feedback_xrc.py	2006-08-10 16:32:38 UTC (rev 11373)
+++ trunk/chandler/application/feedback_xrc.py	2006-08-10 17:56:35 UTC (rev 11374)
@@ -0,0 +1,70 @@
+# This file was automatically generated by pywxrc, do not edit by hand.
+# -*- coding: UTF-8 -*-
+
+# Unfortunately HAVE to edit because of res.Load path issue
+
+#   Copyright (c) 2006 Open Source Applications Foundation
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import wx
+import wx.xrc as xrc
+
+__res = None
+
+def get_resources():
+    """ This function provides access to the XML resources in this module."""
+    global __res
+    if __res == None:
+        __init_resources()
+    return __res
+
+
+class xrcFRAME(wx.Frame):
+    def PreCreate(self):
+        """ This function is called during the class's initialization.
+        
+        Override it for custom setup before the window is created usually to
+        set additional window styles using SetWindowStyle() and SetExtraStyle()."""
+        pass
+
+    def __init__(self, parent):
+        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
+        pre = wx.PreFrame()
+        get_resources().LoadOnFrame(pre, parent, "FRAME")
+        self.PreCreate()
+        self.PostCreate(pre)
+
+        # Define variables for the controls
+        self.panel = xrc.XRCCTRL(self, "panel")
+        self.requiredPage = xrc.XRCCTRL(self, "requiredPage")
+        self.text = xrc.XRCCTRL(self, "text")
+        self.optionalPage = xrc.XRCCTRL(self, "optionalPage")
+        self.email = xrc.XRCCTRL(self, "email")
+        self.comments = xrc.XRCCTRL(self, "comments")
+        self.delButton = xrc.XRCCTRL(self, "delButton")
+        self.sysInfo = xrc.XRCCTRL(self, "sysInfo")
+        self.sendButton = xrc.XRCCTRL(self, "sendButton")
+        self.closeButton = xrc.XRCCTRL(self, "closeButton")
+
+
+
+# ------------------------ Resource data ----------------------
+
+def __init_resources():
+    global __res
+    __res = xrc.EmptyXmlResource()
+
+    # Have to edit the path to the xrc file
+    import os
+    __res.Load(os.path.join(os.path.dirname(__file__), 'feedback.xrc'))




_______________________________________________
Commits mailing list
[email protected]
http://lists.osafoundation.org/mailman/listinfo/commits

Reply via email to