[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