[z3-checkins] r33710 - in z3/hurry.file/trunk: . src/hurry/file src/hurry/file/browser
faassen at codespeak.net
faassen at codespeak.net
Wed Oct 25 11:11:31 CEST 2006
Author: faassen
Date: Wed Oct 25 11:11:28 2006
New Revision: 33710
Added:
z3/hurry.file/trunk/src/hurry/file/tests.py
Modified:
z3/hurry.file/trunk/CHANGES.txt
z3/hurry.file/trunk/setup.py
z3/hurry.file/trunk/src/hurry/file/README.txt
z3/hurry.file/trunk/src/hurry/file/__init__.py
z3/hurry.file/trunk/src/hurry/file/browser/tests.py
z3/hurry.file/trunk/src/hurry/file/configure.zcml
z3/hurry.file/trunk/src/hurry/file/file.py
z3/hurry.file/trunk/src/hurry/file/interfaces.py
Log:
Add tramline support through the IFileRetrieval interface.
Modified: z3/hurry.file/trunk/CHANGES.txt
==============================================================================
--- z3/hurry.file/trunk/CHANGES.txt (original)
+++ z3/hurry.file/trunk/CHANGES.txt Wed Oct 25 11:11:28 2006
@@ -1,6 +1,21 @@
hurry.file changes
==================
+1.0 (2006-10-25)
+----------------
+
+* Support for Tramline (fast file uploads/downloads) through
+ IFileRetrieval. By default, nothing changes.
+
+ If a subclass of TramlineFileRetrievalBase is registered as a
+ IFileRetrieval utility, hurry.file becomes Tramline aware. If files
+ are created manually, they can be created through the
+ createHurryFile function, or the 'createFile' method of the
+ IFileRetrieval service. This will take care of storing the file in
+ the right place.
+
+ Tramline can be found here: http://codespeak.net/svn/rr/tramline/trunk
+
0.9.3 (2006-10-23)
------------------
Modified: z3/hurry.file/trunk/setup.py
==============================================================================
--- z3/hurry.file/trunk/setup.py (original)
+++ z3/hurry.file/trunk/setup.py Wed Oct 25 11:11:28 2006
@@ -2,7 +2,7 @@
setup(
name="hurry.file",
- version="0.9.3",
+ version="1.0",
packages=find_packages('src'),
package_dir= {'':'src'},
@@ -18,7 +18,8 @@
description="""\
hurry.file is an advanced Zope 3 file widget which tries its best to behave
like other widgets, even when the form is redisplayed due to a validation
-error.
+error. It also has built-in support for fast Apache-based file uploads
+and downloads through Tramline.
""",
license='BSD',
keywords="zope zope3",
Modified: z3/hurry.file/trunk/src/hurry/file/README.txt
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/README.txt (original)
+++ z3/hurry.file/trunk/src/hurry/file/README.txt Wed Oct 25 11:11:28 2006
@@ -1,3 +1,124 @@
-File widgets that behave like text widgets.
+This document is about file storage. For information about the actual
+widgets, see also browser/file.txt
-For more information, see browser/file.txt
+The file widget is built on top of the HurryFile object::
+
+ >>> from hurry.file import HurryFile
+ >>> file = HurryFile('foo.txt', 'mydata')
+ >>> file.filename
+ 'foo.txt'
+ >>> file.data
+ 'mydata'
+ >>> f = file.file
+ >>> f.read()
+ 'mydata'
+
+We can also create HurryFile objects from file-like objects::
+
+ >>> from StringIO import StringIO
+ >>> from zope import component
+ >>> from hurry.file.interfaces import IFileRetrieval
+ >>> fileretrieval = component.getUtility(IFileRetrieval)
+ >>> file = fileretrieval.createFile('bar.txt', StringIO('test data'))
+ >>> file.filename
+ 'bar.txt'
+ >>> file.data
+ 'test data'
+ >>> f = file.file
+ >>> f.read()
+ 'test data'
+
+This does exactly the same, but may be easier to use::
+
+ >>> from hurry.file import createHurryFile
+ >>> file = createHurryFile('test2.txt', StringIO('another test file'))
+ >>> file.filename
+ 'test2.txt'
+
+The HurryFile object normally stores the file data using ZODB
+persistence. Files can however also be stored by tramline. If
+tramline is installed in Apache, the Tramline takes care of generating
+ids for files and storing the file on the filesystem directly. The ids
+are then passed as file data to be stored in the ZODB.
+
+Let's first enable tramline.
+
+The tramline directory structure is a directory with two subdirectories,
+one called 'repository' and the other called 'upload'::
+
+ >>> import tempfile, os
+ >>> dirpath = tempfile.mkdtemp()
+ >>> repositorypath = os.path.join(dirpath, 'repository')
+ >>> uploadpath = os.path.join(dirpath, 'upload')
+ >>> os.mkdir(repositorypath)
+ >>> os.mkdir(uploadpath)
+
+We create a TramlineFileRetrieval object knowing about this directory,
+and register it as a utility::
+
+ >>> from hurry.file.file import TramlineFileRetrievalBase
+ >>> class TramlineFileRetrieval(TramlineFileRetrievalBase):
+ ... def getTramlinePath(self):
+ ... return dirpath
+ >>> retrieval = TramlineFileRetrieval()
+ >>> component.provideUtility(retrieval, IFileRetrieval)
+
+Now let's store a file the way tramline would during upload::
+
+ >>> f = open(os.path.join(repositorypath, '1'), 'wb')
+ >>> f.write('test data')
+ >>> f.close()
+
+The file with the data '1' will now be created::
+
+ >>> file = HurryFile('foo.txt', '1')
+
+The data is now '1'::
+
+ >>> file.data
+ '1'
+
+Retrieving the file results in the real file::
+
+ >>> f = file.file
+ >>> f.read()
+ 'test data'
+
+It should be possible to create Hurry File objects that are stored in
+the directory structure directly::
+
+ >>> file = retrieval.createFile('test.txt', StringIO('my test data'))
+ >>> file.filename
+ 'test.txt'
+
+We get an id for the data now::
+
+ >>> file.data != 'my test data'
+ True
+
+And we can retrieve the file itself::
+
+ >>> f = file.file
+ >>> f.read()
+ 'my test data'
+
+Now let's disable tramline in our utility::
+
+ >>> class TramlineFileRetrieval(TramlineFileRetrievalBase):
+ ... def getTramlinePath(self):
+ ... return dirpath
+ ... def isTramlineEnabled(self):
+ ... return False
+ >>> component.provideUtility(TramlineFileRetrieval(), IFileRetrieval)
+
+We expect the same behavior as when tramline is not installed::
+
+ >>> file = HurryFile('foo.txt', 'data')
+ >>> f = file.file
+ >>> f.read()
+ 'data'
+
+Clean up::
+
+ >>> import shutil
+ >>> shutil.rmtree(dirpath)
Modified: z3/hurry.file/trunk/src/hurry/file/__init__.py
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/__init__.py (original)
+++ z3/hurry.file/trunk/src/hurry/file/__init__.py Wed Oct 25 11:11:28 2006
@@ -1,2 +1,2 @@
# this is a package
-from file import HurryFile
+from file import HurryFile, createHurryFile
Modified: z3/hurry.file/trunk/src/hurry/file/browser/tests.py
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/browser/tests.py (original)
+++ z3/hurry.file/trunk/src/hurry/file/browser/tests.py Wed Oct 25 11:11:28 2006
@@ -1,16 +1,22 @@
import unittest
+from zope import component
from zope.testing import doctest
from zope.app.testing import placelesssetup
-
-def workflowSetUp(doctest):
+from zope import component
+
+from hurry.file.file import IdFileRetrieval
+from hurry.file.interfaces import IFileRetrieval
+
+def fileSetUp(doctest):
placelesssetup.setUp()
+ component.provideUtility(IdFileRetrieval(), IFileRetrieval)
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite(
'file.txt',
- setUp=workflowSetUp, tearDown=placelesssetup.tearDown,
+ setUp=fileSetUp, tearDown=placelesssetup.tearDown,
),
))
Modified: z3/hurry.file/trunk/src/hurry/file/configure.zcml
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/configure.zcml (original)
+++ z3/hurry.file/trunk/src/hurry/file/configure.zcml Wed Oct 25 11:11:28 2006
@@ -2,6 +2,9 @@
xmlns="http://namespaces.zope.org/zope"
>
+ <utility provides=".interfaces.IFileRetrieval"
+ factory=".file.IdFileRetrieval" />
+
<!-- the widget is not yet the default widget for FileUpload; use
formlib's custom_widget system to use it. This may change -->
Modified: z3/hurry.file/trunk/src/hurry/file/file.py
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/file.py (original)
+++ z3/hurry.file/trunk/src/hurry/file/file.py Wed Oct 25 11:11:28 2006
@@ -1,7 +1,10 @@
+import sys, os, random, threading
from StringIO import StringIO
from persistent import Persistent
from zope.interface import implements
from hurry.file import interfaces
+from zope import component
+from zope.app.container.contained import Contained
class HurryFile(Persistent):
implements(interfaces.IHurryFile)
@@ -12,10 +15,11 @@
self.headers = {}
def _get_file(self):
- return StringIO(self.data)
+ storage = component.getUtility(interfaces.IFileRetrieval)
+ return storage.getFile(self.data)
file = property(_get_file)
-
+
def __eq__(self, other):
try:
return (self.filename == other.filename and
@@ -30,3 +34,74 @@
except AttributeError:
return True
+def createHurryFile(filename, f):
+ retrieval = component.getUtility(interfaces.IFileRetrieval)
+ return retrieval.createFile(filename, f)
+
+class IdFileRetrieval(Persistent, Contained):
+ """Very basic implementation of FileRetrieval.
+
+ This implementation just returns a File object for the data.
+ """
+ implements(interfaces.IFileRetrieval)
+
+ def getFile(self, data):
+ return StringIO(data)
+
+ def createFile(self, filename, f):
+ return HurryFile(filename, f.read())
+
+class TramlineFileRetrievalBase(Persistent, Contained):
+ """File retrieval for tramline (base class).
+ """
+ implements(interfaces.IFileRetrieval)
+
+ def getTramlinePath(self):
+ raise NotImplementedError
+
+ def isTramlineEnabled(self):
+ return True
+
+ def getFile(self, data):
+ # tramline is disabled, so give fall-back behavior for testing
+ # without tramline
+ if not self.isTramlineEnabled():
+ return StringIO(data)
+ # we need to retrieve the actual file from the filesystem
+ # it could be either a permanently stored file in the repository,
+ # or, if that isn't available, potentially a file in the upload
+ # directory
+ path = self.getTramlinePath()
+ if not path:
+ raise ValueError("No tramline path configured")
+ repository_path = os.path.join(path, 'repository', data)
+ try:
+ f = open(repository_path, 'rb')
+ except IOError:
+ upload_path = os.path.join(path, 'upload', data)
+ f = open(upload_path, 'rb')
+ return f
+
+ def createFile(self, filename, f):
+ repository_path = os.path.join(self.getTramlinePath(),
+ 'repository')
+ # XXX try to make this thread-safe, but it's not 100% ZEO safe..
+ lock = threading.Lock()
+ lock.acquire()
+ try:
+ while True:
+ file_id = str(random.randrange(sys.maxint))
+ if os.path.exists(os.path.join(repository_path,
+ file_id)):
+ continue # try again
+ break
+ path = os.path.join(repository_path, file_id)
+ of = open(path, 'wb')
+ finally:
+ lock.release()
+
+ # XXX this can be made more efficient
+ of.write(f.read())
+ of.close()
+
+ return HurryFile(filename, file_id)
Modified: z3/hurry.file/trunk/src/hurry/file/interfaces.py
==============================================================================
--- z3/hurry.file/trunk/src/hurry/file/interfaces.py (original)
+++ z3/hurry.file/trunk/src/hurry/file/interfaces.py Wed Oct 25 11:11:28 2006
@@ -10,3 +10,12 @@
data = Bytes(title=u'Data in file')
file = Attribute('File-like object with data')
headers = Attribute('Headers associated with file')
+
+class IFileRetrieval(Interface):
+ def getFile(data):
+ """Get a file object for file data.
+ """
+
+ def createFile(filename, f):
+ """Given a file object, create a HurryFile with that data in it.
+ """
Added: z3/hurry.file/trunk/src/hurry/file/tests.py
==============================================================================
--- (empty file)
+++ z3/hurry.file/trunk/src/hurry/file/tests.py Wed Oct 25 11:11:28 2006
@@ -0,0 +1,24 @@
+import unittest
+
+from zope.testing import doctest
+from zope.app.testing import placelesssetup
+from zope import component
+
+from hurry.file.file import IdFileRetrieval
+from hurry.file.interfaces import IFileRetrieval
+
+def fileSetUp(doctest):
+ placelesssetup.setUp()
+ component.provideUtility(IdFileRetrieval(), IFileRetrieval)
+
+def test_suite():
+ return unittest.TestSuite((
+ doctest.DocFileSuite(
+ 'README.txt',
+ setUp=fileSetUp, tearDown=placelesssetup.tearDown,
+ ),
+ ))
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
+
More information about the z3-checkins
mailing list