[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