[Zope3-checkins] CVS: Zope3/src/zope/app/fssync - __init__.py:1.2 classes.py:1.2 configure.zcml:1.2 fsdirective.py:1.2 fsregistry.py:1.2 meta.zcml:1.2 syncer.py:1.2

Guido van Rossum guido@python.org
Mon, 5 May 2003 14:01:32 -0400


Update of /cvs-repository/Zope3/src/zope/app/fssync
In directory cvs.zope.org:/tmp/cvs-serv2527/src/zope/app/fssync

Added Files:
	__init__.py classes.py configure.zcml fsdirective.py 
	fsregistry.py meta.zcml syncer.py 
Log Message:
Merge fssync stuff back to trunk.  A few things actually work (I
successfully did a checkout of some files and diffed them; though
commit doesn't seem to work yet), and you know how I love long-living
branches....



=== Zope3/src/zope/app/fssync/__init__.py 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/__init__.py	Mon May  5 14:01:01 2003
@@ -0,0 +1,2 @@
+#
+# This file is necessary to make this directory a package.


=== Zope3/src/zope/app/fssync/classes.py 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/classes.py	Mon May  5 14:01:01 2003
@@ -0,0 +1,131 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Filesystem synchronization classes.
+
+$Id$
+"""
+
+import os
+
+from zope.app.content.file import File
+from zope.app.content.folder import Folder
+from zope.app.interfaces.fssync import IFSAddView, IObjectFile
+from zope.component.interfaces import IPresentationRequest
+from zope.xmlpickle.xmlpickle import dumps
+
+class FSAddView(object):
+    """See IFSAddView."""
+
+    __implements__ = IFSAddView
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+class AddView(FSAddView):
+    """Supports to create a file system representation of zope
+       file type objects
+    """
+
+    def create(self, fs_path=None):
+        if os.path.isdir(fs_path):
+            return Folder()
+        else:
+            return File()
+
+class AttrMapping(object):
+    """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)
+
+class ObjectEntryAdapter(object):
+    """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__)
+
+class Default(ObjectEntryAdapter):
+    """Default File-system representation for objects."""
+
+    __implements__ =  IObjectFile
+
+    def getBody(self):
+        "See IObjectFile"
+        if type(self.context) is str:
+            return self.context
+        return dumps(self.context)
+
+    def setBody(self, body):
+        pass
+
+    def factory(self):
+        "See IObjectEntry"
+        # We have no factory, cause we're a pickle.
+        return None
+
+class FSAddRequest(object):
+    """XXX docstring???"""
+
+    __implements__ = IPresentationRequest
+
+    def getPresentationType(self):
+        return IFSAddView
+
+    def getPresentationSkin(self):
+        return 'default'


=== Zope3/src/zope/app/fssync/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/configure.zcml	Mon May  5 14:01:01 2003
@@ -0,0 +1,20 @@
+<zopeConfigure xmlns="http://namespaces.zope.org/zope">
+
+  <serviceType
+    id="FSRegistryService"
+    interface="zope.app.interfaces.fssync.IGlobalFSSyncService"
+    />
+  
+  <service
+    serviceType="FSRegistryService"
+    component="zope.app.fssync.fsregistry.fsRegistry"
+    />
+
+  <view 
+    factory="zope.app.fssync.classes.AddView"
+    for="zope.app.interfaces.fssync.IContentDirectory"
+    type="zope.app.interfaces.fssync.IFSAddView"
+    name="."
+    />
+
+</zopeConfigure>


=== Zope3/src/zope/app/fssync/fsdirective.py 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/fsdirective.py	Mon May  5 14:01:01 2003
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# Copyright (c) 2001, 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.
+# 
+##############################################################################
+""" Register class directive.
+
+$Id$
+"""
+from zope.app.fssync.fsregistry import provideSynchronizer
+from zope.configuration.action import Action
+
+def registerFSRegistry(_context, class_=None, factory=None):
+    """registerFSRegistry method to register Class and Serializer factory
+    associated with it.
+    """
+    cls = None
+    if class_ is not None:
+       cls = _context.resolve(class_)
+    
+    fct = _context.resolve(factory)
+
+    return [Action(discriminator=('adapter', class_),
+                   callable=provideSynchronizer,
+                   args=(cls, fct))]


=== Zope3/src/zope/app/fssync/fsregistry.py 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/fsregistry.py	Mon May  5 14:01:01 2003
@@ -0,0 +1,71 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Filesystem synchronization registry.
+
+$Id$
+"""
+
+from zope.app.interfaces.fssync import IGlobalFSSyncService
+from zope.exceptions import DuplicationError, NotFoundError
+
+class FSRegistry(object):
+    """Registry Wrapper class.
+
+    This is a maping from Class -> Serializer Factory Method.
+    """
+
+    __implements__ = IGlobalFSSyncService
+
+    def __init__(self):
+        self._class_factory_reg = {}
+        
+    def __call__(self):
+        return self.__init__()
+
+    def getSynchronizer(self, object):
+        """Return factory method for a given class.
+
+        The factory is returned of the object if None of the 
+        Factory method is present return default factory.
+        """
+
+        factory = self._class_factory_reg.get(object.__class__)
+        if factory is None:
+            factory = self._class_factory_reg.get(None)
+            if factory is None:
+                raise NotFoundError
+        return factory(object)
+
+
+    def provideSynchronizer(self,class_, factory):
+        """Set class_, factory into the dictionary."""
+        if class_ in self._class_factory_reg:
+            raise  DuplicationError
+        else:
+            self._class_factory_reg[class_] = factory
+
+    _clear = __init__
+
+
+# The FS registry serializer service instance
+fsRegistry = FSRegistry()
+provideSynchronizer = fsRegistry.provideSynchronizer
+getSynchronizer = fsRegistry.getSynchronizer
+
+_clear = fsRegistry._clear
+
+# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
+from zope.testing.cleanup import addCleanUp
+addCleanUp(_clear)
+del addCleanUp


=== Zope3/src/zope/app/fssync/meta.zcml 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/meta.zcml	Mon May  5 14:01:01 2003
@@ -0,0 +1,13 @@
+<zopeConfigure xmlns='http://namespaces.zope.org/zope'>
+ 
+  <directives namespace="http://namespaces.zope.org/fssync">
+
+    <directive
+      name="adapter"
+      attributes="class_ factory"
+      handler=".fsdirective.registerFSRegistry"
+      />
+
+  </directives>
+
+</zopeConfigure>


=== Zope3/src/zope/app/fssync/syncer.py 1.1 => 1.2 ===
--- /dev/null	Mon May  5 14:01:32 2003
+++ Zope3/src/zope/app/fssync/syncer.py	Mon May  5 14:01:01 2003
@@ -0,0 +1,343 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Filesystem synchronization functions.
+
+$Id$
+"""
+
+import os, string
+
+from zope.component import queryAdapter, getService
+from zope.xmlpickle.xmlpickle import dumps, loads
+from zope.app.interfaces.fssync \
+     import IObjectEntry, IObjectDirectory, IObjectFile
+
+from zope.app.interfaces.annotation import IAnnotations
+from zope.app.interfaces.container import IContainer
+from zope.configuration.name import resolve
+from zope.app.fssync.classes import Default
+from zope.app.traversing import getPath
+from zope.app.fssync.fsregistry import getSynchronizer
+
+def toFS(ob, name, location, mode=None, objpath=None):
+    """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
+    """
+    objectPath = ''
+    # 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
+    syncService = getService(ob, 'FSRegistryService')
+    adapter = syncService.getSynchronizer(ob)
+
+    entries[name] = {'type': adapter.typeIdentifier(),
+                     'factory': adapter.factory(),
+                     }
+
+    try:
+        if mode == 'N' or mode == 'D':
+            objectPath = objpath
+            entries[name]['isNew'] = 'Y'
+        else:
+            objectPath = str(getPath(ob))
+        entries[name]['path'] = objectPath
+    except TypeError:
+        pass
+
+    # 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):
+        data = ''
+        if mode !='C': # is None:
+            if os.path.exists(path):
+                f = open(path, 'r')
+                data = f.read()
+                f.close()
+                open(path, 'w').write(string.strip(data))
+            else:
+                open(path, 'w').write(string.strip(adapter.getBody()))
+            if objectPath:
+                print 'U %s' % (objectPath[1:])
+        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)
+        if data:
+            open(original_path, 'w').write(string.strip(data))
+        else:
+            open(original_path, 'w').write(string.strip(adapter.getBody()))
+
+
+    else:
+        # Directory
+        if objectPath:
+            print 'UPDATING %s' % (objectPath[1:])
+        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({}))
+            elif mode == 'D':
+                admin_dir = os.path.join(path, '@@Zope')
+                os.mkdir(admin_dir)
+                open(dir_entries, 'w').write(dumps({}))
+        else:
+            os.mkdir(path)
+            if mode == 'D':
+                admin_dir = os.path.join(path, '@@Zope')
+                os.mkdir(admin_dir)
+                dir_entries = os.path.join(path, '@@Zope', 'Entries.xml')
+                open(dir_entries, 'w').write(dumps({}))
+
+        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, mode=None):
+    """Synchromize a file from what's on the file system.
+    """
+    msg =''
+    objectPath = ''
+    # 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
+        ob = container[name]
+        syncService = getService(ob, 'FSRegistryService')
+        adapter = syncService.getSynchronizer(ob)
+
+
+        # 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:
+            if entry.get('type') == '__builtin__.str':
+                newOb = open(path).read()
+                _setItem(container, name, newOb, old=1)
+            else:
+                # Special case pickle data
+                oldOb = container[name]
+                newOb = loads(open(path).read())
+                try:
+                    # See if we can and 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]
+    syncService = getService(ob, 'FSRegistryService')
+    adapter = syncService.getSynchronizer(ob)
+
+
+
+    # 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, mode)
+    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, mode)
+    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())
+        if mode is not None and mode != 'T':
+            if string.find(path,'@@Zope')==-1:
+                #copying to original
+                fspath = path
+                f = open(fspath, 'r')
+                data = f.read()
+                f.close()
+                original_path = os.path.join(os.path.dirname(fspath),'@@Zope','Original',os.path.basename(fspath))
+                f = open(original_path, 'w')
+                f.write(string.strip(data))
+                f.close()
+                entries_path = os.path.join(os.path.dirname(fspath),'@@Zope','Entries.xml')
+                entries = loads(open(entries_path).read())
+                if entries[os.path.basename(fspath)].has_key('isNew'):
+                    del entries[os.path.basename(fspath)]['isNew']
+                    open(entries_path, 'w').write(dumps(entries))
+                objectpath = entries[os.path.basename(fspath)]['path']
+                msg = "%s  <--  %s" %(objectpath, string.split(objectpath,'/')[-1])
+                print msg
+
+
+    else:
+        # Directory
+        if not os.path.isdir(path):
+            raise SynchronizationError("Object is directory, but data is file")
+
+        if mode != 'T':
+            entries_path = os.path.join(os.path.dirname(path),'@@Zope','Entries.xml')
+            entries = loads(open(entries_path).read())
+            if entries[os.path.basename(path)].has_key('isNew'):
+                del entries[os.path.basename(path)]['isNew']
+                open(entries_path, 'w').write(dumps(entries))
+
+        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, mode)