[CMF-checkins] CVS: CMF - FSObject.py:1.1 CMFCorePermissions.py:1.4 FSDTMLMethod.py:1.3 FSImage.py:1.3 FSPropertiesObject.py:1.2 FSPythonScript.py:1.4

Martijn Pieters mj@digicool.com
Wed, 11 Apr 2001 15:28:41 -0400 (EDT)


Update of /cvs-repository/CMF/CMFCore
In directory korak:/tmp/cvs-serv1541/CMFCore

Modified Files:
	CMFCorePermissions.py FSDTMLMethod.py FSImage.py 
	FSPropertiesObject.py FSPythonScript.py 
Added Files:
	FSObject.py 
Log Message:
Refactoring of FS* (filesystem based) objects.

Moved common code out to a base class, cleaned up all FS objects to use the
base class and generally clean out cruft.

Nice side-effect; FSDTMLMethods now are searchable through the ZMI (partly
fixes PTK(251)[]).




--- Added File FSObject.py in package CMF ---
##############################################################################
# 
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# This license has been certified as Open Source(tm).
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1. Redistributions in source code must retain the above copyright
#    notice, this list of conditions, and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions, and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# 3. Digital Creations requests that attribution be given to Zope
#    in any manner possible. Zope includes a "Powered by Zope"
#    button that is installed by default. While it is not a license
#    violation to remove this button, it is requested that the
#    attribution remain. A significant investment has been put
#    into Zope, and this effort will continue if the Zope community
#    continues to grow. This is one way to assure that growth.
# 
# 4. All advertising materials and documentation mentioning
#    features derived from or use of this software must display
#    the following acknowledgement:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    In the event that the product being advertised includes an
#    intact Zope distribution (with copyright and license included)
#    then this clause is waived.
# 
# 5. Names associated with Zope or Digital Creations must not be used to
#    endorse or promote products derived from this software without
#    prior written permission from Digital Creations.
# 
# 6. Modified redistributions of any form whatsoever must retain
#    the following acknowledgment:
# 
#      "This product includes software developed by Digital Creations
#      for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
#    Intact (re-)distributions of any official Zope release do not
#    require an external acknowledgement.
# 
# 7. Modifications are encouraged but must be packaged separately as
#    patches to official Zope releases.  Distributions that do not
#    clearly separate the patches from the original work must be clearly
#    labeled as unofficial distributions.  Modifications which do not
#    carry the name Zope may be packaged in any form, as long as they
#    conform to all of the clauses above.
# 
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
#   EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
#   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
#   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
#   OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#   OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
# 
# 
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations.  Specific
# attributions are listed in the accompanying credits file.
# 
##############################################################################
"""Customizable objects that come from the filesystem (base class)."""
__version__='$Revision: 1.1 $'[11:-2]

from string import split
from os import path, stat

import Acquisition, Globals
from AccessControl import ClassSecurityInfo
from OFS.SimpleItem import Item
from DateTime import DateTime

from DirectoryView import expandpath
import CMFCorePermissions

class FSObject(Acquisition.Implicit, Item):
    """FSObject is a base class for all filesystem based look-alikes.
    
    Subclasses of this class mimic ZODB based objects like Image and DTMLMethod,
    but are not directly modifiable from the management interface. They provide
    means to create a TTW editable copy, however.
    """

    # Always empty for FS based, non-editable objects.
    title = ''

    security = ClassSecurityInfo()
    security.declareObjectProtected(CMFCorePermissions.View)

    _file_mod_time = 0

    def __init__(self, id, filepath, fullname=None, properties=None):
        if properties:
            # Since props come from the filesystem, this should be
            # safe.
            self.__dict__.update(properties)

        self.id = id
        self._filepath = filepath
        fp = expandpath(self._filepath)
        
        try: self._file_mod_time = stat(fp)[8]
        except: pass
        self._readFile()

    security.declareProtected(CMFCorePermissions.ViewManagementScreens,
        'manage_doCustomize')
    def manage_doCustomize(self, folder_path, RESPONSE=None):
        """Makes a ZODB Based clone with the same data.

        Calls _createZODBClone for the actual work.
        """

        obj = self._createZODBClone()
        
        id = obj.getId()
        fpath = tuple(split(folder_path, '/'))
        folder = self.restrictedTraverse(fpath)
        folder._verifyObjectPaste(obj, validate_src=0)
        folder._setObject(id, obj)

        if RESPONSE is not None:
            RESPONSE.redirect('%s/%s/manage_main' % (
                folder.absolute_url(), id))

    def _createZODBClone(self):
        """Create a ZODB (editable) equivalent of this object."""
        raise NotImplemented, "This should be implemented in a subclass."

    def _readFile(self):
        """Read the data from the filesystem.
        
        Read the file (indicated by exandpath(self._filepath), and parse the
        data if necessary.
        """
        raise NotImplemented, "This should be implemented in a subclass."

    # Refresh our contents from the filesystem if that is newer and we are
    # running in debug mode.
    def _updateFromFS(self):
        if Globals.DevelopmentMode:
            fp = expandpath(self._filepath)
            try:    mtime=stat(fp)[8]
            except: mtime=0
            if mtime != self._file_mod_time:
                self._file_mod_time = mtime
                self._readFile()

    security.declareProtected(CMFCorePermissions.View, 'get_size')
    def get_size(self):
        """Get the size of the underlying file."""
        fp = expandpath(self._filepath)
        return path.getsize(fp)

    security.declareProtected(CMFCorePermissions.View, 'getModTime')
    def getModTime(self):
        """Return the last_modified date of the file we represent.

        Returns a DateTime instance.
        """
        self._updateFromFS()
        return DateTime(self._file_mod_time)

    security.declareProtected(CMFCorePermissions.ViewManagementScreens,
        'getObjectFSPath')
    def getObjectFSPath(self):
        """Return the path of the file we represent"""
        self._updateFromFS()
        return self._filepath

Globals.InitializeClass(FSObject)

--- Updated File CMFCorePermissions.py in package CMF --
--- CMFCorePermissions.py	2001/02/28 20:36:14	1.3
+++ CMFCorePermissions.py	2001/04/11 19:28:09	1.4
@@ -9,7 +9,7 @@
 ChangePermissions = Permissions.change_permissions
 ViewManagementScreens = Permissions.view_management_screens
 ManageProperties = Permissions.manage_properties
-
+FTPAccess = Permissions.ftp_access
 
 def setDefaultRoles(permission, roles):
     '''

--- Updated File FSDTMLMethod.py in package CMF --
--- FSDTMLMethod.py	2001/04/08 19:18:09	1.2
+++ FSDTMLMethod.py	2001/04/11 19:28:09	1.3
@@ -85,27 +85,23 @@
 """Customizable DTML methods that come from the filesystem."""
 __version__='$Revision$'[11:-2]
 
+from string import split
+from os import path, stat
+
 import Globals
-from Globals import HTML, HTMLFile
+from AccessControl import ClassSecurityInfo, getSecurityManager, Permissions
 from OFS.DTMLMethod import DTMLMethod, decapitate, guess_content_type
-import Acquisition
-from AccessControl import getSecurityManager
-from OFS.SimpleItem import Item_w__name__
+
+from CMFCorePermissions import View, ViewManagementScreens, FTPAccess
 from DirectoryView import registerFileExtension, registerMetaType, expandpath
-from string import split
-from os import path, stat
-from DateTime import DateTime
-from AccessControl import ClassSecurityInfo
-from CMFCorePermissions import View, ViewManagementScreens
-import CMFCorePermissions
+from FSObject import FSObject
 
 
-class FSDTMLMethod (HTML, Acquisition.Implicit, Item_w__name__):
+class FSDTMLMethod(FSObject, Globals.HTML):
     """FSDTMLMethods act like DTML methods but are not directly
     modifiable from the management interface."""
 
     meta_type = 'Filesystem DTML Method'
-    title = ''
 
     manage_options=(
         (
@@ -117,83 +113,44 @@
 
     # Use declarative security
     security = ClassSecurityInfo()
-    security.declareObjectProtected(CMFCorePermissions.View)
-    security.declareProtected(CMFCorePermissions.View, 'index_html',)
-
-    file_mod_time = 0
+    security.declareObjectProtected(View)
 
-    def __init__(self, id, filepath, fullname=None, properties=None):
-        if properties:
-            # Since props come from the filesystem, this should be
-            # safe.
-            self.__dict__.update(properties)
-        self._filepath = filepath
-        data = self._readFile()
-        fp = expandpath(filepath)
-        try: self.file_mod_time = stat(fp)[8]
-        except: pass
-        HTML.__init__(self, data, __name__=id)
-
     security.declareProtected(ViewManagementScreens, 'manage_main')
-    manage_main = HTMLFile('dtml/custdtml', globals())
+    manage_main = Globals.HTMLFile('dtml/custdtml', globals())
 
-    security.declareProtected(ViewManagementScreens, 'manage_doCustomize')
-    def manage_doCustomize(self, folder_path, data=None, RESPONSE=None):
-        '''
-        Makes a DTMLMethod with the same code.
-        '''
-        custFolder = self.getCustomizableObject()
-        fpath = tuple(split(folder_path, '/'))
-        folder = self.restrictedTraverse(fpath)
-        if data is None:
-            data = self.read()
-        id = self.getId()
-        obj = DTMLMethod(data, __name__=id)
-        folder._verifyObjectPaste(obj, validate_src=0)
-        folder._setObject(id, obj)
-        if RESPONSE is not None:
-            RESPONSE.redirect('%s/%s/manage_main' % (
-                folder.absolute_url(), id))
-
-    security.declareProtected(ViewManagementScreens, 'getMethodFSPath')
-    def getMethodFSPath(self):
-        return self._filepath
+    def __init__(self, id, filepath, fullname=None, properties=None):
+        FSObject.__init__(self, id, filepath, fullname, properties)
+        # Normally called via HTML.__init__ but we don't need the rest that
+        # happens there.
+        self.initvars(None, {})
+
+    def _createZODBClone(self):
+        """Create a ZODB (editable) equivalent of this object."""
+        return DTMLMethod(self.read(), __name__=self.getId())
 
     def _readFile(self):
         fp = expandpath(self._filepath)
         file = open(fp, 'rb')
-        try: data = file.read()
+        try:
+            data = file.read()
         finally: file.close()
-        return data
+        self.raw = data
+        self.cook()
 
-    def _updateFromFS(self):
-        if Globals.DevelopmentMode:
-            fp = expandpath(self._filepath)
-            try:    mtime=stat(fp)[8]
-            except: mtime=0
-            if mtime != self.file_mod_time:
-                self.file_mod_time = mtime
-                self.raw = self._readFile()
-                self.cook()
-
-    def __str__(self):
+    # Hook up chances to reload in debug mode
+    security.declarePrivate('read_raw')
+    def read_raw(self):
         self._updateFromFS()
-        return self.read()
-
-    def getId(self):
-        return self.__name__
+        return Globals.HTML.read_raw(self)
 
     #### The following is mainly taken from OFS/DTMLMethod.py ###
         
     index_html=None # Prevent accidental acquisition
 
     # Documents masquerade as functions:
-    class func_code: pass
-    func_code=func_code()
-    func_code.co_varnames='self','REQUEST','RESPONSE'
-    func_code.co_argcount=3
+    func_code = DTMLMethod.func_code
 
-    default_content_type='text/html'
+    default_content_type = 'text/html'
 
     def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw):
         """Render the document given a client object, REQUEST mapping,
@@ -210,12 +167,12 @@
         
             if client is None:
                 # Called as subtemplate, so don't need error propagation!
-                r=apply(HTML.__call__, (self, client, REQUEST), kw)
+                r=apply(Globals.HTML.__call__, (self, client, REQUEST), kw)
                 if RESPONSE is None: result = r
                 else: result = decapitate(r, RESPONSE)
                 return result
 
-            r=apply(HTML.__call__, (self, client, REQUEST), kw)
+            r=apply(Globals.HTML.__call__, (self, client, REQUEST), kw)
             if type(r) is not type('') or RESPONSE is None:
                 return r
 
@@ -230,23 +187,16 @@
             RESPONSE.setHeader('Content-Type', c)
         result = decapitate(r, RESPONSE)
         return result
-
-    def get_size(self):
-        return len(self.raw)
 
-    def validate(self, inst, parent, name, value, md):
-        return getSecurityManager().validate(inst, parent, name, value)
+    validate = DTMLMethod.validate
 
-    security.declareProtected(View, 'manage_FTPget')
-    def manage_FTPget(self):
-        "Get source for FTP download"
-        return self.read()
-
-    security.declareProtected(ViewManagementScreens, 'getModTime')
-    def getModTime(self):
-        '''
-        '''
-        return DateTime(self.file_mod_time)
+    security.declareProtected(FTPAccess, 'manage_FTPget')
+    security.declareProtected(ViewManagementScreens, 'PrincipiaSearchSource',
+        'document_src')
+
+    manage_FTPget = DTMLMethod.manage_FTPget
+    PrincipiaSearchSource = DTMLMethod.PrincipiaSearchSource
+    document_src = DTMLMethod.document_src
 
 Globals.InitializeClass(FSDTMLMethod)
 

--- Updated File FSImage.py in package CMF --
--- FSImage.py	2001/04/08 19:18:09	1.2
+++ FSImage.py	2001/04/11 19:28:09	1.3
@@ -85,214 +85,70 @@
 """Customizable image objects that come from the filesystem."""
 __version__='$Revision$'[11:-2]
 
+import string, os
+
 import Globals
-from Globals import HTMLFile
 from DateTime import DateTime
-import string
-from os import path, stat
-import Acquisition
-from OFS.SimpleItem import Item_w__name__
-import DirectoryView
-from string import split
-from OFS.Image import Image
-import struct
-from cStringIO import StringIO
-from DirectoryView import registerFileExtension, registerMetaType, expandpath
-from webdav.common import rfc1123_date
 from AccessControl import ClassSecurityInfo
-from CMFCorePermissions import ViewManagementScreens, View
-import CMFCorePermissions
-
-def getImageInfo(data):
-    # This function ought to be in OFS/Image.py or perhaps even
-    # in the Python library, but alas it is not as of January 2001.
-    data = str(data)
-    size = len(data)
-    height = -1
-    width = -1
-    content_type = ''
-
-    # handle GIFs   
-    if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
-        # Check to see if content_type is correct
-        content_type = 'image/gif'
-        w, h = struct.unpack("<HH", data[6:10])
-        width = int(w)
-        height = int(h)
-
-    # See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
-    # Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
-    # and finally the 4-byte width, height
-    elif ((size >= 24) and (data[:8] == '\211PNG\r\n\032\n')
-          and (data[12:16] == 'IHDR')):
-        content_type = 'image/png'
-        w, h = struct.unpack(">LL", data[16:24])
-        width = int(w)
-        height = int(h)
-            
-    # Maybe this is for an older PNG version.
-    elif (size >= 16) and (data[:8] == '\211PNG\r\n\032\n'):
-        # Check to see if we have the right content type
-        content_type = 'image/png'
-        w, h = struct.unpack(">LL", data[8:16])
-        width = int(w)
-        height = int(h)
-
-    # handle JPEGs
-    elif (size >= 2) and (data[:2] == '\377\330'):
-        content_type = 'image/jpeg'
-        jpeg = StringIO(data)
-        jpeg.read(2)
-        b = jpeg.read(1)
-        try:
-            while (b and ord(b) != 0xDA):
-                while (ord(b) != 0xFF): b = jpeg.read(1)
-                while (ord(b) == 0xFF): b = jpeg.read(1)
-                if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
-                    jpeg.read(3)
-                    h, w = struct.unpack(">HH", jpeg.read(4))
-                    break
-                else:
-                    jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
-                b = jpeg.read(1)
-            width = int(w)
-            height = int(h)
-        except: pass
-
-    return content_type, width, height
+from webdav.common import rfc1123_date
+from OFS.Image import Image, getImageInfo
 
+from CMFCorePermissions import ViewManagementScreens, View
+from FSObject import FSObject
+from DirectoryView import registerFileExtension, registerMetaType, expandpath
 
-class FSImage (Acquisition.Implicit, Item_w__name__):
+class FSImage(FSObject):
     """FSImages act like images but are not directly
     modifiable from the management interface."""
     # Note that OFS.Image.Image is not a base class because it is mutable.
 
     meta_type = 'Filesystem Image'
-    title = ''
 
     manage_options=(
         {'label':'Customize', 'action':'manage_main'},
-        #{'label':'View', 'action':'view_image_or_file'},
         )
 
     security = ClassSecurityInfo()
-    security.declareObjectProtected(CMFCorePermissions.View)
-
-    file_mod_time = 0
+    security.declareObjectProtected(View)
 
     def __init__(self, id, filepath, fullname=None, properties=None):
-        if properties:
-            # Since props come from the filesystem, this should be
-            # safe.
-            self.__dict__.update(properties)
-        self.__name__ = fullname or id  # Use the whole filename.
-        self.title = ''
-        self._filepath = filepath
-        fp = expandpath(self._filepath)
-        try: self.file_mod_time = stat(fp)[8]
-        except: pass
-        self._readFile()
+        id = fullname or id # Use the whole filename.
+        FSObject.__init__(self, id, filepath, fullname, properties)
 
     security.declareProtected(ViewManagementScreens, 'manage_main')
-    manage_main = HTMLFile('dtml/custimage', globals())
+    manage_main = Globals.HTMLFile('dtml/custimage', globals())
 
-    security.declareProtected(ViewManagementScreens, 'manage_doCustomize')
-    def manage_doCustomize(self, folder_path, data=None, RESPONSE=None):
-        '''
-        Makes an Image with the same data.
-        '''
-        custFolder = self.getCustomizableObject()
-        fpath = tuple(split(folder_path, '/'))
-        folder = self.restrictedTraverse(fpath)
-        if data is None:
-            data = self._readFile()
-        id = self.getId()
-        obj = Image(id, '', data)
-        folder._verifyObjectPaste(obj, validate_src=0)
-        folder._setObject(id, obj)
-        if RESPONSE is not None:
-            RESPONSE.redirect('%s/%s/manage_main' % (
-                folder.absolute_url(), id))
-
-    security.declareProtected(View, 'getImageFSPath')
-    def getImageFSPath(self):
-        '''
-        '''
-        return self._filepath
+    def _createZODBClone(self):
+        return Image(self.getId(), '', self.read())
 
     def _readFile(self):
         fp = expandpath(self._filepath)
         file = open(fp, 'rb')
         try: data = file.read()
         finally: file.close()
-        (self.content_type, self.width, self.height) = getImageInfo(data)
-        return data
 
-    def _updateFromFS(self):
-        if Globals.DevelopmentMode:
-            fp = expandpath(self._filepath)
-            try:    mtime=stat(fp)[8]
-            except: mtime=0
-            if mtime != self.file_mod_time:
-                self.file_mod_time = mtime
-                self._readFile()
+        # Only parse out image info if the file was changed, because this file
+        # is read every time the image is requested.
+        try:    mtime=os.stat(fp)[8]
+        except: mtime=0
+        if mtime != self._file_mod_time:
+            self._file_mod_time = mtime
+            (self.content_type, self.width, self.height) = getImageInfo(data)
 
-    def getId(self):
-        return self.__name__
+        return data
 
     #### The following is mainly taken from OFS/Image.py ###
         
-    def __str__(self):
-        return self.tag()
+    __str__ = Image.__str__
 
+    _image_tag = Image.tag
     security.declareProtected(View, 'tag')
     def tag(self, height=None, width=None, alt=None,
             scale=0, xscale=0, yscale=0, **args):
-        """
-        Generate an HTML IMG tag for this image, with customization.
-        Arguments to self.tag() can be any valid attributes of an IMG tag.
-        'src' will always be an absolute pathname, to prevent redundant
-        downloading of images. Defaults are applied intelligently for
-        'height', 'width', and 'alt'. If specified, the 'scale', 'xscale',
-        and 'yscale' keyword arguments will be used to automatically adjust
-        the output height and width values of the image tag.
-        """
-        self._updateFromFS()
-        if height is None: height=self.height
-        if width is None:  width=self.width
-
-        # Auto-scaling support
-        xdelta = xscale or scale
-        ydelta = yscale or scale
-
-        if xdelta and width:
-            width = str(int(width) * xdelta)
-        if ydelta and height:
-            height = str(int(height) * ydelta)
-
-        result='<img src="%s"' % (self.absolute_url())
-
-        if alt is None:
-            alt=getattr(self, 'title', '')
-        result = '%s alt="%s"' % (result, alt)
-
-        if height:
-            result = '%s height="%s"' % (result, height)
-
-        if width:
-            result = '%s width="%s"' % (result, width)
-
-        if not 'border' in map(string.lower, args.keys()):
-            result = '%s border="0"' % result
-
-        for key in args.keys():
-            value = args.get(key)
-            result = '%s %s="%s"' % (result, key, value)
-
-        return '%s />' % result
-
-    def id(self):
-        return self.__name__
+        # Hook into an oppertunity to reload metadata.
+        self._readFile()
+        return apply(self._image_tag, (height, width, alt, scale, xscale, 
+            yscale), args)
 
     security.declareProtected(View, 'index_html')
     def index_html(self, REQUEST, RESPONSE):
@@ -302,8 +158,8 @@
         Returns the contents of the file or image.  Also, sets the
         Content-Type HTTP header to the objects content type.
         """
-        # HTTP If-Modified-Since header handling.
         data = self._readFile()
+        # HTTP If-Modified-Since header handling.
         header=REQUEST.get_header('If-Modified-Since', None)
         if header is not None:
             header=string.split(header, ';')[0]
@@ -316,51 +172,30 @@
             try:    mod_since=long(DateTime(header).timeTime())
             except: mod_since=None
             if mod_since is not None:
-                last_mod = self.file_mod_time
+                last_mod = self._file_mod_time
                 if last_mod > 0 and last_mod <= mod_since:
+                    # Set header values since apache caching will return
+                    # Content-Length of 0 in response if size is not set here
+                    RESPONSE.setHeader('Last-Modified', rfc1123_date(last_mod))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Content-Length', self.get_size())
                     RESPONSE.setStatus(304)
                     return ''
 
-        RESPONSE.setHeader('Last-Modified', rfc1123_date(self.file_mod_time))
+        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._file_mod_time))
         RESPONSE.setHeader('Content-Type', self.content_type)
         RESPONSE.setHeader('Content-Length', len(data))
 
         return data
 
-    security.declareProtected(View, 'view_image_or_file')
-    def view_image_or_file(self, URL1):
-        """
-        The default view of the contents of the File or Image.
-        """
-        raise 'Redirect', URL1
-
-    security.declareProtected(View, 'get_size')
-    def get_size(self):
-        """Get the size of a file or image.
-
-        Returns the size of the file or image.
-        """
-        fp = expandpath(self._filepath)
-        return path.getsize(fp)
-
     security.declareProtected(View, 'getContentType')
     def getContentType(self):
         """Get the content type of a file or image.
 
         Returns the content type (MIME type) of a file or image.
         """
-        self._updateFromFS()
+        self._readFile()
         return self.content_type
-
-    security.declareProtected(View, 'getModTime')
-    def getModTime(self):
-        '''
-        '''
-        self._updateFromFS()
-        return DateTime(self.file_mod_time)
-
-    security.declareProtected(View, 'manage_FTPget')
-    manage_FTPget = index_html
 
 Globals.InitializeClass(FSImage)
 

--- Updated File FSPropertiesObject.py in package CMF --
--- FSPropertiesObject.py	2001/02/23 14:24:11	1.1
+++ FSPropertiesObject.py	2001/04/11 19:28:09	1.2
@@ -2,7 +2,7 @@
 # 
 # Zope Public License (ZPL) Version 1.0
 # -------------------------------------
-# 
+#
 # Copyright (c) Digital Creations.  All rights reserved.
 # 
 # This license has been certified as Open Source(tm).
@@ -85,44 +85,32 @@
 """Customizable DTML methods that come from the filesystem."""
 __version__='$Revision$'[11:-2]
 
+from string import split, strip
+
 import Globals
-from Globals import HTML, HTMLFile
 import Acquisition
-from AccessControl import getSecurityManager
-from OFS.SimpleItem import Item
 from OFS.Folder import Folder
 from OFS.PropertyManager import PropertyManager
-from DirectoryView import registerFileExtension, registerMetaType, expandpath
-from string import split, join, strip
-from os import path, stat
-from DateTime import DateTime
 from ZPublisher.Converters import get_converter
 from AccessControl import ClassSecurityInfo
+
+from DirectoryView import registerFileExtension, registerMetaType, expandpath
 from CMFCorePermissions import ViewManagementScreens
-import CMFCorePermissions
+from FSObject import FSObject
 
-class FSPropertiesObject (Acquisition.Implicit, Item, PropertyManager):
+class FSPropertiesObject (FSObject, PropertyManager):
     """FSPropertiesObjects simply hold properties."""
 
     meta_type = 'Filesystem Properties Object'
-    title = ''
 
     manage_options = ({'label':'Customize', 'action':'manage_main'},)
-
     
     security = ClassSecurityInfo()
 
-    _file_mod_time = 0
-
-    def __init__(self, id, filepath, fullname=None, properties=None):
-        self.id = id
-        self._filepath = filepath
-        self._readFile()
-
     security.declareProtected(ViewManagementScreens, 'manage_main')
-    manage_main = HTMLFile('dtml/custprops', globals())
+    manage_main = Globals.HTMLFile('dtml/custprops', globals())
 
-    # Make all mutators private.
+    # Declare all (inherited) mutating methods private.
     security.declarePrivate('manage_addProperty',
                             'manage_editProperties',
                             'manage_delProperties',
@@ -132,15 +120,26 @@
                             'manage_changePropertyTypes',)
                                
     security.declareProtected(ViewManagementScreens, 'manage_doCustomize')
-    def manage_doCustomize(self, folder_path, RESPONSE=None):
-        '''
-        Makes a Folder with the same properties.
-        '''
-        custFolder = self.getCustomizableObject()
-        fpath = tuple(split(folder_path, '/'))
-        folder = self.restrictedTraverse(fpath)
-        id = self.getId()
-        obj = Folder(id)
+    def manage_doCustomize(self, folder_path, data=None, RESPONSE=None):
+        """Makes a ZODB Based clone with the same data.
+
+        Calls _createZODBClone for the actual work.
+        """
+        # Overridden here to provide a different redirect target.
+
+        FSObject.manage_doCustomize(self, folder_path, data, RESPONSE)
+
+        if RESPONSE is not None:
+            fpath = tuple(split(folder_path, '/'))
+            folder = self.restrictedTraverse(fpath)
+            RESPONSE.redirect('%s/%s/manage_propertiesForm' % (
+                folder.absolute_url(), self.getId()))
+    
+    def _createZODBClone(self):
+        """Create a ZODB (editable) equivalent of this object."""
+        # Create a Folder to hold the properties.
+        obj = Folder()
+        obj.id = self.getId()
         map = []
         for p in self._properties:
             # This should be secure since the properties come
@@ -150,21 +149,16 @@
                         'type': p['type'],
                         'mode': 'wd',})
         obj._properties = tuple(map)
-        obj.id = id
-        folder._verifyObjectPaste(obj, validate_src=0)
-        folder._setObject(id, obj)
-        if RESPONSE is not None:
-            RESPONSE.redirect('%s/%s/manage_propertiesForm' % (
-                folder.absolute_url(), id))
 
-    security.declareProtected(ViewManagementScreens, 'getObjectFSPath')
-    def getObjectFSPath(self):
-        '''
-        '''
-        self._updateFromFS()
-        return self._filepath
+        return obj
 
     def _readFile(self):
+        """Read the data from the filesystem.
+        
+        Read the file (indicated by exandpath(self._filepath), and parse the
+        data if necessary.
+        """
+
         fp = expandpath(self._filepath)
         file = open(fp, 'rb')
         try: lines = file.readlines()
@@ -189,31 +183,12 @@
                         })
         self._properties = tuple(map)            
 
-    def _updateFromFS(self):
-        if Globals.DevelopmentMode:
-            fp = expandpath(self._filepath)
-            try:    mtime=stat(fp)[8]
-            except: mtime=0
-            if mtime != self._file_mod_time:
-                self._file_mod_time = mtime
-                self._readFile()
-
     if Globals.DevelopmentMode:
         # Provide an opportunity to update the properties.
         def __of__(self, parent):
             self = Acquisition.ImplicitAcquisitionWrapper(self, parent)
             self._updateFromFS()
             return self
-
-    def getId(self):
-        return self.id
-
-##    def propertyMap(self):
-##        """Return a tuple of mappings, giving meta-data for properties."""
-##        # Don't allow changes.
-##        return map(lambda dict: dict.copy(), self._properties)
-
-    
 
 
 Globals.InitializeClass(FSPropertiesObject)

--- Updated File FSPythonScript.py in package CMF --
--- FSPythonScript.py	2001/03/21 18:40:09	1.3
+++ FSPythonScript.py	2001/04/11 19:28:09	1.4
@@ -85,29 +85,23 @@
 """Customizable Python scripts that come from the filesystem."""
 __version__='$Revision$'[11:-2]
 
-import Globals
-from Globals import DTMLFile
-import Acquisition
-from AccessControl import getSecurityManager
-from OFS.SimpleItem import Item
-from DirectoryView import registerFileExtension, registerMetaType, expandpath
 from string import split
 from os import path, stat
-from DateTime import DateTime
-from AccessControl import ClassSecurityInfo
-from CMFCorePermissions import ViewManagementScreens
-import CMFCorePermissions
 
+import Globals
+from AccessControl import ClassSecurityInfo, getSecurityManager
 from Products.PythonScripts.PythonScript import PythonScript
 from Shared.DC.Scripts.Script import Script, defaultBindings
 
+from CMFCorePermissions import ViewManagementScreens, View, FTPAccess
+from DirectoryView import registerFileExtension, registerMetaType, expandpath
+from FSObject import FSObject
 
-class FSPythonScript (Script, Acquisition.Implicit, Item):
+class FSPythonScript (FSObject, Script):
     """FSPythonScripts act like Python Scripts but are not directly
     modifiable from the management interface."""
 
     meta_type = 'Filesystem Script (Python)'
-    title = ''
     _params = _body = ''
 
     manage_options=(
@@ -121,65 +115,34 @@
 
     # Use declarative security
     security = ClassSecurityInfo()
-    security.declareObjectProtected(CMFCorePermissions.View)
-    security.declareProtected(CMFCorePermissions.View, 'index_html',)
-
-    _file_mod_time = 0
+    security.declareObjectProtected(View)
+    security.declareProtected(View, 'index_html',)
 
     def __init__(self, id, filepath, fullname=None, properties=None):
-        self.id = id
-        if properties:
-            # Since props come from the filesystem, this should be
-            # safe.
-            self.__dict__.update(properties)
-        self._filepath = filepath
+        FSObject.__init__(self, id, filepath, fullname, properties)
         self.ZBindings_edit(defaultBindings)
-        self._updateFromFS(1)
 
     security.declareProtected(ViewManagementScreens, 'manage_main')
-    manage_main = DTMLFile('dtml/custpy', globals())
-
-    security.declareProtected(ViewManagementScreens, 'manage_doCustomize')
-    def manage_doCustomize(self, folder_path, body=None, RESPONSE=None):
-        '''
-        Makes a PythonScript with the same code.
-        '''
-        custFolder = self.getCustomizableObject()
-        fpath = tuple(split(folder_path, '/'))
-        folder = self.restrictedTraverse(fpath)
-        if body is None:
-            body = self.read()
-        id = self.getId()
-        obj = PythonScript(id)
-        folder._verifyObjectPaste(obj, validate_src=0)
-        folder._setObject(id, obj)
-        obj = folder._getOb(id)
-        obj.write(body)
-        if RESPONSE is not None:
-            RESPONSE.redirect('%s/%s/manage_main' % (
-                folder.absolute_url(), id))
-
-    security.declareProtected(ViewManagementScreens, 'getMethodFSPath')
-    def getMethodFSPath(self):
-        return self._filepath
-
-    def _updateFromFS(self, force=0):
-        if force or Globals.DevelopmentMode:
-            fp = expandpath(self._filepath)
-            try:    mtime=stat(fp)[8]
-            except: mtime=0
-            if force or mtime != self._file_mod_time:
-                self._file_mod_time = mtime
-                fp = expandpath(self._filepath)
-                file = open(fp, 'rb')
-                try: data = file.read()
-                finally: file.close()
-                self._write(data)
-                self._makeFunction(1)
+    manage_main = Globals.DTMLFile('dtml/custpy', globals())
 
-    def getId(self):
-        self._updateFromFS()
-        return self.id
+    def _createZODBClone(self):
+        """Create a ZODB (editable) equivalent of this object."""
+        obj = PythonScript(self.getId())
+        obj.write(self.read())
+        return obj
+
+    def _readFile(self):
+        """Read the data from the filesystem.
+        
+        Read the file (indicated by exandpath(self._filepath), and parse the
+        data if necessary.
+        """
+        fp = expandpath(self._filepath)
+        file = open(fp, 'rb')
+        try: data = file.read()
+        finally: file.close()
+        self._write(data)
+        self._makeFunction(1)
 
     def _validateProxy(self, roles=None):
         pass
@@ -227,17 +190,14 @@
     read = PythonScript.read
     document_src = PythonScript.document_src
     PrincipiaSearchSource = PythonScript.PrincipiaSearchSource
-    manage_FTPget = PythonScript.manage_FTPget
     params = PythonScript.params
     body = PythonScript.body
     get_size = PythonScript.get_size
 
-    _write = PythonScript.write
+    security.declareProtected(FTPAccess, 'manage_FTPget')
+    manage_FTPget = PythonScript.manage_FTPget
 
-    def getModTime(self):
-        '''
-        '''
-        return DateTime(self._file_mod_time)
+    _write = PythonScript.write
 
     def ZCacheable_invalidate(self):
         # Waaa