[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)