r644 - in zope-externaleditor/branches/upstream/current: . tests win32

Fabio Tranchitella kobold at alioth.debian.org
Fri Feb 9 15:01:51 CET 2007


Author: kobold
Date: 2007-02-09 15:01:50 +0100 (Fri, 09 Feb 2007)
New Revision: 644

Modified:
   zope-externaleditor/branches/upstream/current/CHANGES.txt
   zope-externaleditor/branches/upstream/current/ExternalEditor.py
   zope-externaleditor/branches/upstream/current/manage_main.dtml
   zope-externaleditor/branches/upstream/current/tests/edit.txt
   zope-externaleditor/branches/upstream/current/tests/test_functional.py
   zope-externaleditor/branches/upstream/current/version.txt
   zope-externaleditor/branches/upstream/current/win32/buildexe.bat
   zope-externaleditor/branches/upstream/current/win32/setup.iss
   zope-externaleditor/branches/upstream/current/zopeedit.py
Log:
[svn-upgrade] Integrating new upstream version, zope-externaleditor (0.9.3-src)

Modified: zope-externaleditor/branches/upstream/current/CHANGES.txt
===================================================================
--- zope-externaleditor/branches/upstream/current/CHANGES.txt	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/CHANGES.txt	2007-02-09 14:01:50 UTC (rev 644)
@@ -1,5 +1,12 @@
 External Editor Change Log
 
+  01/03/2007 - 0.9.3 Release
+
+    - Fixed issue with 'manage_FTPget' overriding the 'Content-Type'
+      header.
+
+    - Only run ExpandEnvironmentStrings on win32 systems.
+
   9/14/2006 - 0.9.2 Release
 
     - Added 'skip_data' option to make External Editor send out only

Modified: zope-externaleditor/branches/upstream/current/ExternalEditor.py
===================================================================
--- zope-externaleditor/branches/upstream/current/ExternalEditor.py	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/ExternalEditor.py	2007-02-09 14:01:50 UTC (rev 644)
@@ -11,7 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""$Id: ExternalEditor.py 69097 2006-07-11 19:51:48Z sidnei $
+"""$Id: ExternalEditor.py 71655 2006-12-26 22:12:16Z sidnei $
 """
 
 # Zope External Editor Product by Casey Duncan
@@ -50,7 +50,7 @@
         self.data = data
 
     def __iter__(self):
-        return iter(self)
+        return self
 
     def next(self):
         if self.data is None:
@@ -165,12 +165,6 @@
         metadata = join(r, '\n')
         metadata_len = len(metadata)
 
-        # Using RESPONSE.setHeader('Pragma', 'no-cache') would be better, but
-        # this chokes crappy most MSIE versions when downloads happen on SSL.
-        # cf. http://support.microsoft.com/support/kb/articles/q316/4/31.asp
-        RESPONSE.setHeader('Last-Modified', rfc1123_date())
-        RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
-
         # Check if we should send the file's data down the response.
         if REQUEST.get('skip_data'):
             # We've been requested to send only the metadata. The
@@ -180,10 +174,31 @@
 
         ob_data = getattr(Acquisition.aq_base(ob), 'data', None)
         if (ob_data is not None and isinstance(ob_data, Image.Pdata)):
-            # We have a File instance with chunked data, lets stream it
+            # We have a File instance with chunked data, lets stream it.
+            #
+            # Note we are setting the content-length header here. This
+            # is a simplification. Read comment below.
+            #
+            # We assume that ob.get_size() will return the exact size
+            # of the PData chain. If that assumption is broken we
+            # might have problems. This is mainly an optimization. If
+            # we read the whole PData chain just to compute the
+            # correct size that could cause the whole file to be read
+            # into memory.
             RESPONSE.setHeader('Content-Length', ob.get_size())
+            # It is safe to use this PDataStreamIterator here because
+            # it is consumed right below. This is only used to
+            # simplify the code below so it only has to deal with
+            # stream iterators or plain strings.
             body = PDataStreamIterator(ob.data)
         elif hasattr(ob, 'manage_FTPget'):
+            # Calling manage_FTPget *might* have side-effects. For
+            # example, in Archetypes it does set the 'content-type'
+            # response header, which would end up overriding our own
+            # content-type header because we've set it 'too
+            # early'. We've moved setting the content-type header to
+            # the '_write_metadata' method since, and any manipulation
+            # of response headers should happen there, if possible.
             try:
                 body = ob.manage_FTPget()
             except TypeError: # some need the R/R pair!
@@ -211,10 +226,26 @@
                 RESPONSE.write(data)
             return ''
 
-        # If we reached this point, body *must* be a string.
+        # If we reached this point, body *must* be a string. We *must*
+        # set the headers ourselves since _write_metadata won't get
+        # called.
+        self._set_headers(RESPONSE)
         return join((metadata, body), '\n')
 
+    def _set_headers(self, RESPONSE):
+        # Using RESPONSE.setHeader('Pragma', 'no-cache') would be better, but
+        # this chokes crappy most MSIE versions when downloads happen on SSL.
+        # cf. http://support.microsoft.com/support/kb/articles/q316/4/31.asp
+        RESPONSE.setHeader('Last-Modified', rfc1123_date())
+        RESPONSE.setHeader('Content-Type', 'application/x-zope-edit')
+
     def _write_metadata(self, RESPONSE, metadata, length):
+        # Set response content-type so that the browser gets hinted
+        # about what application should handle this.
+        self._set_headers(RESPONSE)
+
+        # Set response length and write our metadata. The '+1' on the
+        # content-length is the '\n' after the metadata.
         RESPONSE.setHeader('Content-Length', length + 1)
         RESPONSE.write(metadata)
         RESPONSE.write('\n')

Modified: zope-externaleditor/branches/upstream/current/manage_main.dtml
===================================================================
--- zope-externaleditor/branches/upstream/current/manage_main.dtml	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/manage_main.dtml	2007-02-09 14:01:50 UTC (rev 644)
@@ -40,13 +40,15 @@
      onChange="location.href='&dtml-URL1;/'+this.options[this.selectedIndex].value">
     <option value="manage_workspace" disabled>Select type to add...</option>
     <dtml-in filtered_meta_types mapping sort=name>
-    <option value="&dtml.url_quote-action;">&dtml-name;</option>
+    <dtml-if action>
+    <option value="&dtml.html_quote-action;">&dtml-name;</option>
+    </dtml-if>
     </dtml-in>
     </select>
     <input class="form-element" type="submit" name="submit" value=" Add " />
   <dtml-else>
     <dtml-in filtered_meta_types mapping sort=name>
-    <input type="hidden" name=":method" value="&dtml.url_quote-action;" />
+    <input type="hidden" name=":method" value="&dtml.html_quote-action;" />
     <input class="form-element" type="submit" name="submit" value="Add &dtml-name;" />
     </dtml-in>
   </dtml-if>
@@ -99,7 +101,7 @@
   <td width="19%" align="left"><div class="list-item"><a 
    href="./manage_main?skey=bobobase_modification_time<dtml-if 
    "skey == 'bobobase_modification_time' and not rkey"
-   >&amprkey=bobobase_modification_time</dtml-if>"
+   >&amp;rkey=bobobase_modification_time</dtml-if>"
    onMouseOver="window.status='Sort objects by modification time'; return true"
    onMouseOut="window.status=''; return true"><dtml-if 
    "skey == 'bobobase_modification_time' or rkey == 'bobobase_modification_time'"

Modified: zope-externaleditor/branches/upstream/current/tests/edit.txt
===================================================================
--- zope-externaleditor/branches/upstream/current/tests/edit.txt	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/tests/edit.txt	2007-02-09 14:01:50 UTC (rev 644)
@@ -145,6 +145,32 @@
 
   >>> self.folder['some-file'].wl_clearLocks()
 
+
+Create a class that has a 'manage_FTPget' method with
+side-effects. Make sure that the content-type header is properly set
+to 'application/x-zope-edit':
+
+  >>> from Products.ExternalEditor.tests.test_functional import SideEffects
+  >>> _ = self.folder._setObject('another-file', SideEffects('another-file', 'some content'))
+
+  >>> print http(r"""
+  ... GET /test_folder_1_/externalEdit_/another-file HTTP/1.1
+  ... Authorization: Basic %s:%s
+  ... """ % (user_name, user_password))
+  HTTP/1.1 200 OK
+  Content-Length: 140
+  Content-Type: application/x-zope-edit; charset=iso-8859-15
+  Last-Modified:...
+  <BLANKLINE>
+  url:http://localhost/test_folder_1_/another-file
+  meta_type:Side Effects
+  title:
+  auth:...
+  cookie:
+  <BLANKLINE>
+  some content
+
+
 Callback Registry
 =================
 

Modified: zope-externaleditor/branches/upstream/current/tests/test_functional.py
===================================================================
--- zope-externaleditor/branches/upstream/current/tests/test_functional.py	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/tests/test_functional.py	2007-02-09 14:01:50 UTC (rev 644)
@@ -20,6 +20,16 @@
 # Install our product
 ZopeTestCase.installProduct('ExternalEditor')
 
+from OFS.SimpleItem import SimpleItem
+class SideEffects(SimpleItem):
+    meta_type = 'Side Effects'
+    def __init__(self, id, content):
+        self.id = id
+        self.content = content
+    def manage_FTPget(self, REQUEST, RESPONSE):
+        RESPONSE.setHeader('Content-Type', 'text/plain')
+        return self.content
+
 def test_suite():
     import unittest
     suite = unittest.TestSuite()

Modified: zope-externaleditor/branches/upstream/current/version.txt
===================================================================
--- zope-externaleditor/branches/upstream/current/version.txt	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/version.txt	2007-02-09 14:01:50 UTC (rev 644)
@@ -1 +1 @@
-0.9.2
+0.9.3

Modified: zope-externaleditor/branches/upstream/current/win32/buildexe.bat
===================================================================
--- zope-externaleditor/branches/upstream/current/win32/buildexe.bat	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/win32/buildexe.bat	2007-02-09 14:01:50 UTC (rev 644)
@@ -1,3 +1,4 @@
 cd ..
-c:\python23\python setup.py py2exe -p Plugins -p win32com -e Tkinter -p encodings 
+c:\python24\python setup.py py2exe -p Plugins -p win32com -e Tkinter -p encodings -p MFC71
+cd win32
 pause

Modified: zope-externaleditor/branches/upstream/current/win32/setup.iss
===================================================================
--- zope-externaleditor/branches/upstream/current/win32/setup.iss	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/win32/setup.iss	2007-02-09 14:01:50 UTC (rev 644)
@@ -3,10 +3,10 @@
 [Setup]
 DisableStartupPrompt=yes
 AppName=Zope External Editor Helper Application
-AppVerName=Zope External Editor 0.9.2
+AppVerName=Zope External Editor 0.9.3
 AppPublisher=Casey Duncan, Zope Corporation (maintained by Chris McDonough)
 AppPublisherURL=http://plope.com/software/ExternalEditor
-AppVersion=0.9.2
+AppVersion=0.9.3
 AppSupportURL=http://plope.com/software/ExternalEditor
 AppUpdatesURL=http://plope.com/software/ExternalEditor
 DefaultDirName={pf}\ZopeExternalEditor
@@ -14,7 +14,7 @@
 AllowNoIcons=yes
 LicenseFile=..\LICENSE.txt
 ChangesAssociations=yes
-OutputBaseFilename=zopeedit-win32-0.9.2
+OutputBaseFilename=zopeedit-win32-0.9.3
 
 [Registry]
 ; Register file type for use by helper app

Modified: zope-externaleditor/branches/upstream/current/zopeedit.py
===================================================================
--- zope-externaleditor/branches/upstream/current/zopeedit.py	2007-02-09 14:01:25 UTC (rev 643)
+++ zope-externaleditor/branches/upstream/current/zopeedit.py	2007-02-09 14:01:50 UTC (rev 644)
@@ -14,15 +14,18 @@
 ##############################################################################
 """Zope External Editor Helper Application by Casey Duncan
 
-$Id: zopeedit.py 70187 2006-09-15 04:09:15Z chrism $"""
+$Id: zopeedit.py 71558 2006-12-15 11:19:41Z wichert $"""
 
-__version__ = '0.9.2'
+__version__ = '0.9.3'
 
 import sys
 
 win32 = sys.platform == 'win32'
 
 if win32:
+    # import pywin32 stuff first so it never looks into system32
+    import pythoncom, pywintypes
+    
     # prevent warnings from being turned into errors by py2exe
     import warnings
     warnings.filterwarnings('ignore')
@@ -30,12 +33,18 @@
 import os, re
 import rfc822
 import traceback
-from tempfile import mktemp
+import logging
+import urllib
+import shutil
+
+from tempfile import mktemp, NamedTemporaryFile
 from ConfigParser import ConfigParser
 from httplib import HTTPConnection, HTTPSConnection
 from urlparse import urlparse
-import urllib
 
+logger = logging.getLogger('zopeedit')
+log_file = None
+
 class Configuration:
     
     def __init__(self, path):
@@ -95,22 +104,47 @@
     did_lock = 0
     
     def __init__(self, input_file):
+        global log_file
+        log_file = NamedTemporaryFile(suffix='-zopeedit-log.txt')
+
+        self.input_file = input_file
+
+        # Setup logging.
+        logging.basicConfig(stream=log_file,
+                            level=logging.DEBUG)
+        logger.info('Opening %r.', input_file)
+    
         try:
             # Read the configuration file
             if win32:
                 # Check the home dir first and then the program dir
                 config_path = os.path.expanduser('~\\ZopeEdit.ini')
-                global_config = os.path.join(sys.path[0] or '', 'ZopeEdit.ini')
-                if not os.path.exists(config_path) \
-                   and os.path.exists(global_config):
+
+                # sys.path[0] might be library.zip!!!!
+                app_dir = sys.path[0]
+                if app_dir.lower().endswith('library.zip'):
+                    app_dir = os.path.dirname(app_dir)
+                global_config = os.path.join(app_dir or '', 'ZopeEdit.ini')
+
+                if not os.path.exists(config_path):
+                    logger.debug('Config file %r does not exist. '
+                                 'Using global configuration file: %r.',
+                                 config_path, global_config)
+
+                    # Don't check for the existence of the global
+                    # config file. It will be created anyway.
                     config_path = global_config
+                else:
+                    logger.debug('Using user configuration file: %r.',
+                                 config_path)
+                    
             else:
                 config_path = os.path.expanduser('~/.zope-external-edit')
                 
             self.config = Configuration(config_path)
 
             # Open the input file and read the metadata headers
-            in_f = open(input_file, 'rU')
+            in_f = open(input_file, 'rb')
             m = rfc822.Message(in_f)
 
             self.metadata = metadata = m.dict.copy()
@@ -125,6 +159,9 @@
                                             metadata.get('content_type',''),
                                             self.host)
 
+            # Should we keep the log file?
+            self.keep_log = int(self.options.get('keep_log', 0))
+
             # Write the body of the input file to a separate file
             if int(self.options.get('long_file_name', 1)):
                 sep = self.options.get('file_name_separator', ',')
@@ -148,8 +185,10 @@
             else:
                 content_file = mktemp(content_file)
                 
+            logger.debug('Destination filename will be: %r.', content_file)
+            
             body_f = open(content_file, 'wb')
-            body_f.write(in_f.read())
+            shutil.copyfileobj(in_f, body_f)
             self.content_file = content_file
             self.saved = 1
             in_f.close()
@@ -157,8 +196,10 @@
             self.clean_up = int(self.options.get('cleanup_files', 1))
             if self.clean_up: 
                 try:
+                    logger.debug('Cleaning up %r.', input_file)
                     os.remove(input_file)
                 except OSError:
+                    logger.exception('Failed to clean up %r.', input_file)
                     pass # Sometimes we aren't allowed to delete it
             
             if self.ssl:
@@ -190,11 +231,29 @@
             try:
                 os.remove(self.content_file)
             except OSError:
+                logger.exception('Failed to clean up %r', self.content_file)
                 pass     
 
         if self.did_lock:
             # Try not to leave dangling locks on the server
-            self.unlock(interactive=0)
+            try:
+                self.unlock(interactive=0)
+            except:
+                logger.exception('Failure during unlock.')
+
+        if getattr(self, 'keep_log', 0):
+            if log_file is not None:
+                base = getattr(self, 'content_file', '')
+                if not base:
+                    base = getattr(self, 'input_file', 'noname')
+                base = os.path.basename(base)
+                fname = mktemp(suffix='-zopeedit-log.txt',
+                               prefix='%s-' % base)
+                bkp_f = open(fname, 'wb')
+
+                # Copy the log file to a backup file.
+                log_file.seek(0)
+                shutil.copyfileobj(log_file, bkp_f)
             
     def getEditorCommand(self):
         """Return the editor command"""
@@ -203,11 +262,14 @@
         if win32 and editor is None:
             from _winreg import HKEY_CLASSES_ROOT, OpenKeyEx, \
                                 QueryValueEx, EnumKey
-            from win32api import FindExecutable
-            import pywintypes
+            from win32api import FindExecutable, ExpandEnvironmentStrings
+
             # Find editor application based on mime type and extension
             content_type = self.metadata.get('content_type')
             extension = self.options.get('extension')
+
+            logger.debug('Have content type: %r, extension: %r',
+                         content_type, extension)
             
             if content_type:
                 # Search registry for the extension by MIME type
@@ -215,6 +277,9 @@
                     key = 'MIME\\Database\\Content Type\\%s' % content_type
                     key = OpenKeyEx(HKEY_CLASSES_ROOT, key)
                     extension, nil = QueryValueEx(key, 'Extension')
+                    logger.debug('Registry has extension %r for '
+                                 'content type %r',
+                                 extension, content_type)
                 except EnvironmentError:
                     pass
             
@@ -225,71 +290,90 @@
                 if dot != -1 and dot > url.rfind('/'):
                     extension = url[dot:]
 
+                    logger.debug('Extracted extension from url: %r',
+                                 extension)
+
+            classname = editor = None
             if extension is not None:
                 try:
                     key = OpenKeyEx(HKEY_CLASSES_ROOT, extension)
                     classname, nil = QueryValueEx(key, None)
+                    logger.debug('ClassName for extension %r is: %r',
+                                 extension, classname)
                 except EnvironmentError:
                     classname = None
 
-                if classname is not None:
-                    try:
-                        # Look for Edit action in registry
-                        key = OpenKeyEx(HKEY_CLASSES_ROOT, 
-                                        classname+'\\Shell\\Edit\\Command')
-                        editor, nil = QueryValueEx(key, None)
-                    except EnvironmentError:
-                        pass
+            if classname is not None:
+                try:
+                    # Look for Edit action in registry
+                    key = OpenKeyEx(HKEY_CLASSES_ROOT, 
+                                    classname+'\\Shell\\Edit\\Command')
+                    editor, nil = QueryValueEx(key, None)
+                    logger.debug('Edit action for %r is: %r',
+                                 classname, editor)
+                except EnvironmentError:
+                    pass
 
-                    if editor is None:
-                        # Enumerate the actions looking for one
-                        # starting with 'Edit'
+            if classname is not None and editor is None:
+                logger.debug('Could not find Edit action for %r. '
+                             'Brute-force enumeration.', classname)
+                # Enumerate the actions looking for one
+                # starting with 'Edit'
+                try:
+                    key = OpenKeyEx(HKEY_CLASSES_ROOT, 
+                                    classname+'\\Shell')
+                    index = 0
+                    while 1:
                         try:
-                            key = OpenKeyEx(HKEY_CLASSES_ROOT, 
-                                            classname+'\\Shell')
-                            index = 0
-                            while 1:
-                                try:
-                                    subkey = EnumKey(key, index)
-                                    index += 1
-                                    if str(subkey).lower().startswith('edit'):
-                                        subkey = OpenKeyEx(key, 
-                                                           subkey + 
-                                                           '\\Command')
-                                        editor, nil = QueryValueEx(subkey, 
-                                                                   None)
-                                    else:
-                                        continue
-                                except EnvironmentError:
-                                    break
+                            subkey = EnumKey(key, index)
+                            index += 1
+                            if str(subkey).lower().startswith('edit'):
+                                subkey = OpenKeyEx(key, subkey + '\\Command')
+                                editor, nil = QueryValueEx(subkey, 
+                                                           None)
+                            if editor is None:
+                                continue
+                            logger.debug('Found action %r for %r. '
+                                         'Command will be: %r',
+                                         subkey, classname, editor)
                         except EnvironmentError:
-                            pass
+                            break
+                except EnvironmentError:
+                    pass
 
-                    if editor is None:
-                        try:
-                            # Look for Open action in registry
-                            key = OpenKeyEx(HKEY_CLASSES_ROOT, 
-                                            classname+'\\Shell\\Open\\Command')
-                            editor, nil = QueryValueEx(key, None)
-                        except EnvironmentError:
-                            pass
+            if classname is not None and editor is None:
+                try:
+                    # Look for Open action in registry
+                    key = OpenKeyEx(HKEY_CLASSES_ROOT, 
+                                    classname+'\\Shell\\Open\\Command')
+                    editor, nil = QueryValueEx(key, None)
+                    logger.debug('Open action for %r has command: %r. ',
+                                 classname, editor)
+                except EnvironmentError:
+                    pass
 
-                if editor is None:
-                    try:
-                        nil, editor = FindExecutable(self.content_file, '')
-                    except pywintypes.error:
-                        pass
+            if editor is None:
+                try:
+                    nil, editor = FindExecutable(self.content_file, '')
+                    logger.debug('Executable for %r is: %r. ',
+                                 self.content_file, editor)
+                except pywintypes.error:
+                    pass
             
             # Don't use IE as an "editor"
             if editor is not None and editor.find('\\iexplore.exe') != -1:
+                logger.debug('Found iexplore.exe. Skipping.')
                 editor = None
 
-        if editor is not None:            
-            return editor
-        else:
+            if editor is not None:            
+                return ExpandEnvironmentStrings(editor)
+
+        if editor is None:
             fatalError('No editor was found for that object.\n'
                        'Specify an editor in the configuration file:\n'
                        '(%s)' % self.config.path)
+
+        return editor
         
     def launch(self):
         """Launch external editor"""
@@ -322,6 +406,8 @@
         else:
             bin = command
 
+        logger.debug('Command %r, will use %r', command, bin)
+
         if bin is not None:
             # Try to load the plugin for this editor
             try:
@@ -329,6 +415,8 @@
                 Plugin = __import__(module, globals(), locals(), 
                                     ('EditorProcess',))
                 editor = Plugin.EditorProcess(self.content_file)
+                logger.debug('Launching Plugin %r with: %r',
+                             Plugin, self.content_file)
             except (ImportError, AttributeError):
                 bin = None
 
@@ -344,6 +432,7 @@
             else:
                 command = '%s %s' % (command, self.content_file)
 
+            logger.debug('Launching EditorProcess with: %r', command)
             editor = EditorProcess(command)
             
         launch_success = editor.isAlive()
@@ -558,7 +647,6 @@
     from win32con import MB_OK, MB_OKCANCEL, MB_YESNO, MB_RETRYCANCEL, \
                          MB_SYSTEMMODAL, MB_ICONERROR, MB_ICONQUESTION, \
                          MB_ICONEXCLAMATION
-    import pywintypes
 
     def errorDialog(message):
         MessageBox(message, title, MB_OK + MB_ICONERROR + MB_SYSTEMMODAL)
@@ -577,6 +665,7 @@
         def __init__(self, command):
             """Launch editor process"""
             try:
+                logger.debug('CreateProcess: %r', command)
                 self.handle, nil, nil, nil = CreateProcess(None, command, None, 
                                                            None, 1, 0, None, 
                                                            None, STARTUPINFO())
@@ -660,10 +749,16 @@
 
 def fatalError(message, exit=1):
     """Show error message and exit"""
+    global log_file
     errorDialog('FATAL ERROR: %s' % message)
     # Write out debug info to a temp file
     debug_f = open(mktemp('-zopeedit-traceback.txt'), 'w')
     try:
+        # Copy the log_file before it goes away on a fatalError.
+        if log_file is not None:
+            log_file.seek(0)
+            shutil.copyfileobj(log_file, debug_f)
+            print >> debug_f, '-' * 80
         traceback.print_exc(file=debug_f)
     finally:
         debug_f.close()




More information about the pkg-zope-commits mailing list