[Checkins] SVN: zope.fssync/trunk/s Merge changes from amos-snarf branch to trunk. Now snarf format works

Amos Latteier amos at latteier.com
Fri Feb 20 15:30:05 EST 2009


Log message for revision 96852:
  Merge changes from amos-snarf branch to trunk. Now snarf format works 
  with empty directories.
  

Changed:
  U   zope.fssync/trunk/setup.py
  U   zope.fssync/trunk/src/zope/fssync/interfaces.py
  U   zope.fssync/trunk/src/zope/fssync/snarf.py
  U   zope.fssync/trunk/src/zope/fssync/tests/test_snarf.py

-=-
Modified: zope.fssync/trunk/setup.py
===================================================================
--- zope.fssync/trunk/setup.py	2009-02-20 20:12:04 UTC (rev 96851)
+++ zope.fssync/trunk/setup.py	2009-02-20 20:30:05 UTC (rev 96852)
@@ -35,7 +35,11 @@
       namespace_packages=['zope',],
       tests_require = ['zope.testing'],
       install_requires=['setuptools',
+                        'zope.annotation',
+                        'zope.component',
+                        'zope.filerepresentation',
                         'zope.interface',
+                        'zope.lifecycleevent',
                         'zope.proxy',
                         'zope.traversing',
                         'zope.xmlpickle'],

Modified: zope.fssync/trunk/src/zope/fssync/interfaces.py
===================================================================
--- zope.fssync/trunk/src/zope/fssync/interfaces.py	2009-02-20 20:12:04 UTC (rev 96851)
+++ zope.fssync/trunk/src/zope/fssync/interfaces.py	2009-02-20 20:30:05 UTC (rev 96852)
@@ -18,6 +18,7 @@
 __docformat__ = "reStructuredText"
 
 from zope import interface
+import zope.interface.common.mapping
 from zope import component
 from zope import schema
 from zope import lifecycleevent
@@ -31,7 +32,7 @@
 
     def modify(target):
         """Modifies the target annotations.
-        
+
         Transfers the synchronizable namespaces to the target annotations.
         Returns an lifecycleevent.interfaces.IModificationDescription
         if changes were detected, None othewise.
@@ -40,7 +41,7 @@
 
 class IObjectSynchronized(lifecycleevent.interfaces.IModificationDescription):
     """A unspecific modification description.
-    
+
     Basically says that an object has changed during a sync
     operation. If you can say more specific things you should
     use other modification descriptions.
@@ -48,12 +49,12 @@
 
 class IRepository(interface.Interface):
     """A target system that stores objects as files or directories."""
-    
+
     chunk_size = schema.Int(
         title=u"Chunk Size",
         description=u"The chunk size.",
         default=32768)
-        
+
     case_insensitive = schema.Bool(
         title=u"Case Insensitive",
         description=u"Is this repository case insensitive?",
@@ -96,7 +97,7 @@
 
 class IUnpickler(interface.Interface):
     """An unpickler."""
-    
+
     def load(readable):
         """Loads a pickled object from a readable file-like object."""
 
@@ -109,7 +110,7 @@
 
     def __str__():
         """Returns a string representation.
-        
+
         The encoding should be 'UTF-8'.
         """
 
@@ -153,7 +154,7 @@
 class ISyncTask(interface.Interface):
     """Base interface for ICheckout, ICommit, and ICheck.
 
-    The repository may be a filesystem, an archive, a database, 
+    The repository may be a filesystem, an archive, a database,
     or something else that is able to store serialized data.
     """
 
@@ -200,14 +201,14 @@
         name -- The name of the object
 
         location -- The location where the object will go
-        
+
         Raises a ``SynchronizationError`` if the object
         already exists at the given location.
         """
 
 
 class ICheck(ISyncTask):
-    """Check that the repository is consistent with the object database.""" 
+    """Check that the repository is consistent with the object database."""
 
     def perform(container, name, fspath):
         """Compare an object or object tree from a repository.
@@ -233,7 +234,7 @@
 
 class IFileSystemRepository(IRepository):
     """A filesystem repository.
-    
+
     Stores the data in a directory tree on the filesystem.
     """
 
@@ -253,14 +254,14 @@
 
 class ISynchronizerFactory(component.interfaces.IFactory):
     """A factory for synchronizer, i.e. serializers/de-serializers.
-    
-    The factory should be registered as a named utility with 
-    the dotted name of the adapted class as the lookup key. 
-    
+
+    The factory should be registered as a named utility with
+    the dotted name of the adapted class as the lookup key.
+
     The default factory should be registered without a name.
-    
+
     The call of the factory should return
-    
+
     - an `IDirectorySynchronizer` adapter for the object if the
       object is represented as a directory.
 
@@ -284,14 +285,14 @@
     def annotations():
         """Return annotations for the entry.
 
-        Returns None if the serializer provides 
+        Returns None if the serializer provides
         it's own representation
         """
 
     def extras():
         """Return extra data for the entry.
 
-        Returns None if the serializer provides it's own 
+        Returns None if the serializer provides it's own
         representation of extras.
         """
 
@@ -299,17 +300,17 @@
 class IDeserializer(interface.Interface):
     """The inverse operator of an ISerializer.
 
-    Deserializer consume serialized data and provide 
+    Deserializer consume serialized data and provide
     write access to parts of the deserialized objects.
     """
 
     def setmetadata(metadata):
         """Sets entry metadata.
-        
+
         Returns an lifecycleevent.interfaces.IModificationDescription
         if relevant changes were detected, None othewise.
         """
-        
+
     def setannotations(annotations):
         """Sets deserialized annotations.
 
@@ -371,7 +372,7 @@
         """Deletes an item."""
 
 
-class IDirectorySynchronizer(ISynchronizer, 
+class IDirectorySynchronizer(ISynchronizer,
                                 IDirectorySerializer, IDirectoryDeserializer):
     """A synchronizer for directory-like objects."""
 
@@ -381,7 +382,7 @@
 
 class IObjectGenerator(interface.Interface):
     """A generator for objects with a special create protocol."""
-    
+
     def create(context, name):
         """Creates the object in the given context."""
 

Modified: zope.fssync/trunk/src/zope/fssync/snarf.py
===================================================================
--- zope.fssync/trunk/src/zope/fssync/snarf.py	2009-02-20 20:12:04 UTC (rev 96851)
+++ zope.fssync/trunk/src/zope/fssync/snarf.py	2009-02-20 20:30:05 UTC (rev 96852)
@@ -2,14 +2,14 @@
 #
 # Copyright (c) 2003 Zope Corporation and Contributors.
 # All Rights Reserved.
-# 
+#
 # This software is subject to the provisions of the Zope Public License,
 # Version 2.1 (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.
-# 
+#
 ##############################################################################
 """Simple New ARchival Format (SNARF).
 
@@ -20,8 +20,9 @@
 
     '<size> <pathname>\n'
 
-followed by exactly <size> bytes.  Directories are not represented
-explicitly.
+followed by exactly <size> bytes.  Directories are represented by
+paths that end in / and have a zero size. The root directory has a
+blank path.
 
 Pathnames are always relative and always use '/' for delimiters, and
 should not use '.' or '..' or '' as components.  All files are read
@@ -62,6 +63,7 @@
                 return True
         names = os.listdir(root)
         names.sort()
+        self.adddir(prefix)
         for name in names:
             fspath = os.path.join(root, name)
             if not filter(fspath):
@@ -83,6 +85,10 @@
         finally:
             f.close()
 
+    def adddir(self, path):
+        path = fsutil.encode(path, 'utf-8')
+        self.ostr.write("0 %s\n" % path)
+
     def addstream(self, istr, size, path):
         """Snarf a single file from a data stream.
 
@@ -120,13 +126,18 @@
             infoline = infoline[:-1]
             sizestr, path = infoline.split(" ", 1)
             size = int(sizestr)
-            f = self.createfile(path)
-            try:
-                copybytes(size, self.istr, f)
-            finally:
-                f.close()
+            if size == 0 and (path == "" or path.endswith("/")):
+                self.makedir(path)
+            else:
+                f = self.createfile(path)
+                try:
+                    copybytes(size, self.istr, f)
+                finally:
+                    f.close()
 
     def makedir(self, path):
+        if path.endswith('/'):
+            path = path[:-1]
         fspath = self.translatepath(path)
         self.ensuredir(fspath)
 
@@ -140,6 +151,8 @@
             os.makedirs(fspath)
 
     def translatepath(self, path):
+        if path == "":
+            return self.root
         if ":" in path and os.name != "posix":
             raise IOError("path cannot contain colons: $r" % path)
         if "\\" in path and os.name != "posix":

Modified: zope.fssync/trunk/src/zope/fssync/tests/test_snarf.py
===================================================================
--- zope.fssync/trunk/src/zope/fssync/tests/test_snarf.py	2009-02-20 20:12:04 UTC (rev 96851)
+++ zope.fssync/trunk/src/zope/fssync/tests/test_snarf.py	2009-02-20 20:30:05 UTC (rev 96852)
@@ -74,6 +74,8 @@
         tfn = self.maketree()
         self.snf.addtree(tfn)
         self.assertEqual(self.ostr.getvalue(),
+                         "0 \n"
+                         "0 d1/\n"
                          "8 d1/f1\n"   "d1f1data"
                          "6 f1\n"      "f1data"
                          "7 f1~\n"     "f1adata"
@@ -83,6 +85,8 @@
         tfn = self.maketree()
         self.snf.addtree(tfn, "top/")
         self.assertEqual(self.ostr.getvalue(),
+                         "0 top/\n"
+                         "0 top/d1/\n"
                          "8 top/d1/f1\n"   "d1f1data"
                          "6 top/f1\n"      "f1data"
                          "7 top/f1~\n"     "f1adata"
@@ -92,6 +96,8 @@
         tfn = self.maketree()
         self.snf.addtree(tfn, filter=lambda x: not x.endswith("~"))
         self.assertEqual(self.ostr.getvalue(),
+                         "0 \n"
+                         "0 d1/\n"
                          "8 d1/f1\n"   "d1f1data"
                          "6 f1\n"      "f1data"
                          "6 f2\n"      "f2data")
@@ -106,11 +112,31 @@
         tfn = self.maketree()
         self.snf.add(tfn, "top")
         self.assertEqual(self.ostr.getvalue(),
+                         "0 top/\n"
+                         "0 top/d1/\n"
                          "8 top/d1/f1\n"   "d1f1data"
                          "6 top/f1\n"      "f1data"
                          "7 top/f1~\n"     "f1adata"
                          "6 top/f2\n"      "f2data")
 
+    def test_empty_directories(self):
+        """
+        Make sure that empty directories show up in snarf
+        """
+        tfn = self.tempdir()
+        d1 = os.path.join(tfn, "d1")
+        d2 = os.path.join(tfn, "d2")
+        d3 = os.path.join(d1, "d3")
+        os.mkdir(d1)
+        os.mkdir(d2)
+        os.mkdir(d3)
+        self.snf.addtree(tfn)
+        self.assertEqual(self.ostr.getvalue(),
+                         "0 \n"
+                         "0 d1/\n"
+                         "0 d1/d3/\n"
+                         "0 d2/\n")
+        
     def maketree(self):
         tfn = self.tempdir()
         f1 = os.path.join(tfn, "f1")
@@ -125,7 +151,7 @@
         self.writefile("d1f1data", d1f1)
         return tfn
 
-class TestUnsnarfer(unittest.TestCase):
+class TestUnsnarfer(TempFiles):
 
     def test_translatepath(self):
         snf = Unsnarfer(StringIO(""))
@@ -137,10 +163,66 @@
         self.assertRaises(IOError, snf.translatepath, "a//b")
         self.assertRaises(IOError, snf.translatepath, "/a")
         self.assertRaises(IOError, snf.translatepath, "a/")
-        self.assertRaises(IOError, snf.translatepath, "")
 
     # TODO: More to add...
 
+    def test_unsnarf(self):
+        data = ("0 \n"
+                "0 d1/\n"
+                "8 d1/f1\n"   "d1f1data"
+                "6 f1\n"      "f1data"
+                "7 f1~\n"     "f1adata"
+                "6 f2\n"      "f2data")
+        f = StringIO(data)
+        f.seek(0)
+        snf = Unsnarfer(f)
+        tfn = self.tempdir()
+        snf.unsnarf(tfn)
+        self.assertEqual(open(os.path.join(tfn, 'f1')).read(),
+                         'f1data')
+        self.assertEqual(open(os.path.join(tfn, 'f1~')).read(),
+                         'f1adata')
+        self.assertEqual(open(os.path.join(tfn, 'f2')).read(),
+                         'f2data')
+        self.assertEqual(open(os.path.join(tfn, 'd1', 'f1')).read(),
+                         'd1f1data')
+
+    def test_unsnarf_prefix(self):
+        data = ("0 top/\n"
+                "0 top/d1/\n"
+                "8 top/d1/f1\n"   "d1f1data"
+                "6 top/f1\n"      "f1data"
+                "7 top/f1~\n"     "f1adata"
+                "6 top/f2\n"      "f2data")
+        f = StringIO(data)
+        f.seek(0)
+        snf = Unsnarfer(f)
+        tfn = self.tempdir()
+        snf.unsnarf(tfn)
+        self.assertEqual(open(os.path.join(tfn, 'top', 'f1')).read(),
+                         'f1data')
+        self.assertEqual(open(os.path.join(tfn, 'top', 'f1~')).read(),
+                         'f1adata')
+        self.assertEqual(open(os.path.join(tfn, 'top', 'f2')).read(),
+                         'f2data')
+        self.assertEqual(open(os.path.join(tfn, 'top', 'd1', 'f1')).read(),
+                         'd1f1data')
+        
+    def test_empty_directories(self):
+        data = ("0 \n"
+                "0 d1/\n"
+                "0 d1/d3/\n"
+                "0 d2/\n")
+        f = StringIO(data)
+        f.seek(0)
+        snf = Unsnarfer(f)
+        tfn = self.tempdir()
+        snf.unsnarf(tfn)
+        self.assertTrue(os.path.isdir(os.path.join(tfn, 'd1')))
+        self.assertTrue(os.path.isdir(os.path.join(tfn, 'd2')))
+        self.assertTrue(os.path.isdir(os.path.join(tfn, 'd1', 'd3')))
+        
+    
 def test_suite():
     s = unittest.TestSuite()
     s.addTest(unittest.makeSuite(TestCopyBytes))



More information about the Checkins mailing list