[Zope3-checkins] CVS: Zope3/lib/python/Zope/App/FSSync - AttrMapping.py:1.2 Default.py:1.2 IObjectDirectory.py:1.2 IObjectEntry.py:1.2 IObjectFile.py:1.2 ObjectEntryAdapter.py:1.2 Syncer.py:1.2 __init__.py:1.2 configure.zcml:1.2

Jim Fulton jim@zope.com
Fri, 11 Oct 2002 02:28:36 -0400


Update of /cvs-repository/Zope3/lib/python/Zope/App/FSSync
In directory cvs.zope.org:/tmp/cvs-serv1649/lib/python/Zope/App/FSSync

Added Files:
	AttrMapping.py Default.py IObjectDirectory.py IObjectEntry.py 
	IObjectFile.py ObjectEntryAdapter.py Syncer.py __init__.py 
	configure.zcml 
Log Message:
adding (back) changed to branch

=== Zope3/lib/python/Zope/App/FSSync/AttrMapping.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/AttrMapping.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+__metaclass__ = type
+
+class AttrMapping:
+    """Convenience object implementing a mapping on selected object attributes
+    """
+
+    def __init__(self, context, attrs, schema=None):
+        self.attrs = attrs
+        self.context = context
+
+    def __getitem__(self, name):
+        if name in self.attrs:
+            return getattr(self.context, name)
+        raise KeyError, name
+
+    def get(self, name, default):
+        if name in self.attrs:
+            return getattr(self.context, name, default)
+        return default
+
+    def __contains__(self, name):
+        return (name in self.attrs) and hasattr(self.context, name)
+
+    def __delitem__(self, name):
+        if name in self.attrs:
+            delattr(self.context, name)
+            return
+        raise KeyError, name
+
+    def __setitem__(self, name, value):
+        if name in self.attrs:
+            setattr(self.context, name, value)
+            return
+        raise KeyError, name
+
+    def __iter__(self):
+        return iter(self.attrs)
+
+__doc__ = AttrMapping.__doc__ + __doc__
+


=== Zope3/lib/python/Zope/App/FSSync/Default.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/Default.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+__metaclass__ = type
+
+from Zope.App.FSSync.ObjectEntryAdapter import ObjectEntryAdapter
+from Zope.App.FSSync.IObjectFile import IObjectFile
+from Zope.XMLPickle.XMLPickle import dumps
+
+
+class Default(ObjectEntryAdapter):
+    """Default File-system representation for objects
+    """
+
+    __implements__ =  IObjectFile
+
+    def getBody(self):
+        "See Zope.App.FSSync.IObjectFile.IObjectFile"
+        if type(self.context) is str:
+            return self.context
+        return dumps(self.context)
+
+    def setBody(self, body):
+        pass
+    
+    def factory(self):
+        "See Zope.App.FSSync.IObjectEntry.IObjectEntry"
+        # We have no factory, cause we're a pickle.
+
+
+__doc__ = Default.__doc__ + __doc__
+


=== Zope3/lib/python/Zope/App/FSSync/IObjectDirectory.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/IObjectDirectory.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+from IObjectEntry import IObjectEntry
+
+class IObjectDirectory(IObjectEntry):
+    """File-system object representation for directory-like objects
+    """
+
+    def contents():
+        """Return the contents
+
+        A sequence of name, value object are returned. The value in each
+        pair will be syncronized.
+        """
+
+__doc__ = IObjectDirectory.__doc__ + __doc__


=== Zope3/lib/python/Zope/App/FSSync/IObjectEntry.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/IObjectEntry.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+from Interface import Interface
+
+class IObjectEntry(Interface):
+    """File-system object representation
+    """
+
+    def extra():
+        """Return extra data for the entry.
+
+        The data are returned as a mapping object that allows *both*
+        data retrieval and setting.  The mapping is from names to
+        objects that will be serialized to or from the file system.
+
+        """
+
+    def typeIdentifier():
+        """Return a dotted name that identifies the object type.
+
+        This is typically a dotted class name.
+
+        This is used when synchronizing from the file system to the
+        database to decide whether the existing object and the new
+        object are of the same type.
+        
+        """
+
+    def factory():
+        """Return the dotted name of a factory to recreate an empty entry.
+
+        The factory will be called with no arguments. It is usually
+        the dotted name of the object class.
+        
+        """
+
+__doc__ = IObjectEntry.__doc__ + __doc__


=== Zope3/lib/python/Zope/App/FSSync/IObjectFile.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/IObjectFile.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+from IObjectEntry import IObjectEntry
+
+class IObjectFile(IObjectEntry):
+    """File-system object representation for file-like objects
+    """
+
+    def getBody():
+        """Return the file body"""
+
+    def setBody():
+        """Change the file body"""
+
+__doc__ = IObjectFile.__doc__ + __doc__


=== Zope3/lib/python/Zope/App/FSSync/ObjectEntryAdapter.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/ObjectEntryAdapter.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,43 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""
+$Id$
+"""
+
+__metaclass__ = type
+
+class ObjectEntryAdapter:
+    """Convenience Base class for ObjectEntry adapter implementations
+    """
+
+    def __init__(self, context):
+        self.context = context
+
+    def extra(self):
+        "See Zope.App.FSSync.IObjectEntry.IObjectEntry"
+
+    def typeIdentifier(self):
+        "See Zope.App.FSSync.IObjectEntry.IObjectEntry"
+        class_ = self.context.__class__
+        return "%s.%s" % (class_.__module__, class_.__name__)
+
+    def factory(self):
+        "See Zope.App.FSSync.IObjectEntry.IObjectEntry"
+        # Return the dotted class name, assuming that it can be used
+        class_ = self.context.__class__
+        return "%s.%s" % (class_.__module__, class_.__name__)
+    
+
+__doc__ = ObjectEntryAdapter.__doc__ + __doc__
+


=== Zope3/lib/python/Zope/App/FSSync/Syncer.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/Syncer.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,272 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################
+"""XXX short summary goes here.
+
+XXX longer description goes here.
+
+$Id$
+"""
+__metaclass__ = type
+
+import os
+from Zope.ComponentArchitecture import getAdapter, queryAdapter
+from Zope.XMLPickle.XMLPickle import dumps, loads
+from IObjectEntry import IObjectEntry
+from IObjectDirectory import IObjectDirectory
+from IObjectFile import IObjectFile
+from Zope.App.OFS.Annotation.IAnnotations import IAnnotations
+from Zope.App.OFS.Container.IContainer import IContainer
+from Zope.Configuration.name import resolve
+from Default import Default
+
+def toFS(ob, name, location):
+    """Check an object out to the file system
+
+    ob -- The object to be checked out
+
+    name -- The name of the object
+
+    location -- The directory on the file system where the object will go
+    """
+
+    # Look for location admin dir
+    admin_dir = os.path.join(location, '@@Zope')
+    if not os.path.exists(admin_dir):
+        os.mkdir(admin_dir)
+    
+    # Open Entries file
+    entries_path = os.path.join(admin_dir, "Entries.xml")
+    if os.path.exists(entries_path):
+        entries = loads(open(entries_path).read())
+    else:
+        entries = {}
+
+    # Get the object adapter
+    adapter = getAdapter(ob, IObjectEntry)
+
+    entries[name] = {'type': adapter.typeIdentifier(),
+                     'factory': adapter.factory(),
+                     }
+
+    # Write entries file
+    open(entries_path, 'w').write(dumps(entries))
+
+
+    # Get name path and check that name is not an absolute path
+    path = os.path.join(location, name)
+    if path == name:
+        raise ValueError("Invalid absolute path name")
+    
+
+    # Handle extras
+    extra = adapter.extra()
+    if extra:
+        extra_dir = os.path.join(admin_dir, 'Extra')
+        if not os.path.exists(extra_dir):
+            os.mkdir(extra_dir)
+        extra_dir = os.path.join(extra_dir, name)
+        if not os.path.exists(extra_dir):
+            os.mkdir(extra_dir)
+        for ename in extra:
+            edata = extra[ename]
+            toFS(edata, ename, extra_dir)
+
+    # Handle annotations
+    annotations = queryAdapter(ob, IAnnotations)
+    if annotations is not None:
+        annotation_dir = os.path.join(admin_dir, 'Annotations')
+        if not os.path.exists(annotation_dir):
+            os.mkdir(annotation_dir)
+        annotation_dir = os.path.join(annotation_dir, name)
+        if not os.path.exists(annotation_dir):
+            os.mkdir(annotation_dir)
+        for key in annotations:
+            annotation = annotations[key]
+            toFS(annotation, key, annotation_dir)
+        
+
+    # Handle data
+    if IObjectFile.isImplementedBy(adapter):
+        open(path, 'w').write(adapter.getBody())
+        original_path = os.path.join(admin_dir, 'Original')
+        if not os.path.exists(original_path):
+            os.mkdir(original_path)
+        original_path = os.path.join(original_path, name)
+        open(original_path, 'w').write(adapter.getBody())
+        
+
+    else:
+        # Directory
+        if os.path.exists(path):
+            dir_entries = os.path.join(path, '@@Zope', 'Entries.xml')
+            if os.path.exists(dir_entries):
+                open(dir_entries, 'w').write(dumps({}))
+        else:
+            os.mkdir(path)
+            
+        
+        for cname, cob in  adapter.contents():
+            toFS(cob, cname, path)
+        
+class SynchronizationError(Exception): pass
+
+def _setItem(container, name, ob, old=0):
+    # Set an item in a container or in a mapping
+    if IContainer.isImplementedBy(container):
+        if old:
+            del container[name]
+        newName = container.setObject(name, ob)
+        if newName != name:
+            raise SynchronizationError(
+                "Container generated new name for %s" % path)
+    else:
+        # Not a container, must be a mapping
+        container[name] = ob
+
+def fromFS(container, name, location):
+    """Synchromize a file from what's on the file system.
+    """
+
+    # Look for location admin dir
+    admin_dir = os.path.join(location, '@@Zope')
+    if not os.path.exists(admin_dir):
+        raise SynchronizationError("No @@Zope admin directory, %s" % admin_dir)
+        
+    # Open Entries file
+    entries_path = os.path.join(admin_dir, "Entries.xml")
+    entries = loads(open(entries_path).read())
+    entry = entries[name]
+    factory = entry.get('factory')
+
+    # Get name path and check that name is not an absolute path
+    path = os.path.join(location, name)
+    if path == name:
+        raise ValueError("Invalid absolute path name")
+
+    # See if this is an existing object
+    if name in container:
+        # Yup, let's see if we have the same kind of object
+    
+        # Get the object adapter
+        adapter = getAdapter(container[name], IObjectEntry)
+        
+        # Replace the object if the type is different
+        if adapter.typeIdentifier() != entry.get('type'):
+            # We have a different object, replace the one that's there
+
+            if factory:            
+                newOb = resolve(factory)()
+            else:
+                newOb = loads(open(path).read())
+
+            _setItem(container, name, newOb, old=1)
+
+        elif not factory:
+            # Special case pickle data
+            oldOb = container[name]
+            newOb = loads(open(path).read())
+            try:
+                # See id we can amd should just copy the state
+                oldOb._p_oid # Is it persisteny
+                getstate = newOb.__getstate__
+            except AttributeError:
+                # Nope, we have to replace.
+                _setItem(container, name, newOb, old=1)
+            else:
+                oldOb.__setstate__(getstate())
+                oldOb._p_changed = 1
+                
+
+    else:
+        # We need to create a new object
+
+        if factory:            
+            newOb = resolve(entry['factory'])()
+        else:
+            newOb = loads(open(path).read())
+
+        _setItem(container, name, newOb)
+        
+
+    # Get the object adapter again
+    ob = container[name]
+    adapter = getAdapter(ob, IObjectEntry)
+    
+
+    # Handle extra
+    extra = adapter.extra()
+    extra_dir = os.path.join(admin_dir, 'Extra', name)
+    extra_entries_path = os.path.join(extra_dir, "@@Zope", "Entries.xml")
+    if extra:
+        if not os.path.exists(extra_entries_path):
+            # The file system has no extras, so delete all of the object's
+            # extras.
+            for key in extra:
+                del extra[key]
+        else:
+            extra_entries = loads(
+                open(extra_entries_path).read())
+            for ename in extra_entries:
+                fromFS(extra, ename, extra_dir)
+    elif os.path.exists(extra_entries_path):
+        extra_entries = loads(
+            open(extra_entries_path).read())
+        if extra_entries:
+            raise SynchronizationError(
+                "File-system extras for object with no extra data")
+    
+
+    # Handle annotations
+    annotations = queryAdapter(ob, IAnnotations)
+    annotation_dir = os.path.join(admin_dir, 'Annotations', name)
+    annotation_entries_path = os.path.join(
+        annotation_dir, "@@Zope", "Entries.xml")
+    if annotations is not None:
+        if not os.path.exists(annotation_entries_path):
+            # The file system has no annotations, so delete all of the object's
+            # annotations.
+            for key in annotations:
+                del annotations[key]
+        else:
+            annotation_entries = loads(
+                open(annotation_entries_path).read())
+            for ename in annotation_entries:
+                fromFS(annotations, ename, annotation_dir)
+    elif os.path.exists(annotation_entries_path):
+        annotation_entries = loads(
+            open(annotation_entries_path).read())
+        if annotation_entries:
+            raise SynchronizationError(
+                "File-system annotations for non annotatable object")
+
+    # Handle data
+    if IObjectFile.isImplementedBy(adapter):
+        # File
+        if os.path.isdir(path):
+            raise SynchronizationError("Object is file, but data is directory")
+        adapter.setBody(open(path).read())
+
+    else:
+        # Directory
+        if not os.path.isdir(path):
+            raise SynchronizationError("Object is directory, but data is file")
+
+        dir_entries_path = os.path.join(path, '@@Zope', 'Entries.xml')
+        dir_entries = loads(open(dir_entries_path).read())
+
+        for cname in dir_entries:
+            fromFS(ob, cname, path)
+    
+    
+        


=== Zope3/lib/python/Zope/App/FSSync/__init__.py 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/__init__.py	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,13 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+# 
+##############################################################################


=== Zope3/lib/python/Zope/App/FSSync/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Fri Oct 11 02:28:36 2002
+++ Zope3/lib/python/Zope/App/FSSync/configure.zcml	Fri Oct 11 02:28:05 2002
@@ -0,0 +1,6 @@
+<zopeConfigure xmlns="http://namespaces.zope.org/zope">
+
+<adapter provides="Zope.App.FSSync.IObjectFile."
+         factory=".Default." />
+
+</zopeConfigure>