[Checkins] SVN: z3c.extfile/trunk/ added static namespace,
see CHANGES.txt
Bernd Dorn
bernd.dorn at lovelysystems.com
Wed May 23 05:03:13 EDT 2007
Log message for revision 75902:
added static namespace, see CHANGES.txt
Changed:
U z3c.extfile/trunk/CHANGES.txt
U z3c.extfile/trunk/setup.py
U z3c.extfile/trunk/src/z3c/extfile/browser/configure.zcml
A z3c.extfile/trunk/src/z3c/extfile/browser/views.py
U z3c.extfile/trunk/src/z3c/extfile/configure.zcml
A z3c.extfile/trunk/src/z3c/extfile/datamanager.py
U z3c.extfile/trunk/src/z3c/extfile/hashdir.py
U z3c.extfile/trunk/src/z3c/extfile/hashdir.txt
U z3c.extfile/trunk/src/z3c/extfile/interfaces.py
A z3c.extfile/trunk/src/z3c/extfile/namespace.py
A z3c.extfile/trunk/src/z3c/extfile/namespace.txt
A z3c.extfile/trunk/src/z3c/extfile/namespace.zcml
U z3c.extfile/trunk/src/z3c/extfile/property.py
U z3c.extfile/trunk/src/z3c/extfile/tests.py
-=-
Modified: z3c.extfile/trunk/CHANGES.txt
===================================================================
--- z3c.extfile/trunk/CHANGES.txt 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/CHANGES.txt 2007-05-23 09:03:12 UTC (rev 75902)
@@ -8,3 +8,7 @@
- Use product config z3c.extfile:storagedir to set the storage directory
- Registration of utility is now logged on INFO
+
+- New ++static++ namespace and index.html view for IReadFile, see
+ namespace.txt. This adds a dependency to z3c.filetype.
+
Modified: z3c.extfile/trunk/setup.py
===================================================================
--- z3c.extfile/trunk/setup.py 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/setup.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -19,17 +19,23 @@
namespace_packages = ['z3c'],
zip_safe = False,
extras_require={"test":['zope.testing',
+ 'zope.app.testing',
]},
install_requires = ['setuptools',
+ 'z3c.filetype',
+ 'zope.app.component',
'zope.app.appsetup',
'zope.app.file',
'zope.app.form',
'zope.app.wsgi',
+ 'zope.cachedescriptors',
'zope.contenttype',
+ 'zope.datetime',
'zope.formlib',
'zope.i18nmessageid',
'zope.publisher',
'zope.schema',
- 'zope.security'
+ 'zope.security',
+ 'zope.traversing',
],
)
Modified: z3c.extfile/trunk/src/z3c/extfile/browser/configure.zcml
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/browser/configure.zcml 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/browser/configure.zcml 2007-05-23 09:03:12 UTC (rev 75902)
@@ -4,13 +4,38 @@
i18n_domain="zope"
>
- <view
- type="zope.publisher.interfaces.browser.IBrowserRequest"
- for="..interfaces.IExtBytesField"
- provides="zope.app.form.interfaces.IInputWidget"
- factory=".widget.ExtBytesWidget"
- permission="zope.Public"
- />
-
+ <view
+ type="zope.publisher.interfaces.browser.IBrowserRequest"
+ for="..interfaces.IExtBytesField"
+ provides="zope.app.form.interfaces.IInputWidget"
+ factory=".widget.ExtBytesWidget"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="..interfaces.IReadFile"
+ name="index.html"
+ permission="zope.View"
+ class=".views.ReadFileView"/>
+
+ <view
+ for="..interfaces.IReadFile"
+ name="absolute_url"
+ factory=".views.ReadFileAbsoluteURL"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ permission="zope.Public"
+ allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
+ />
+
+ <view
+ for="..interfaces.IReadFile"
+ factory=".views.ReadFileAbsoluteURL"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ permission="zope.Public"
+ provides="zope.traversing.browser.interfaces.IAbsoluteURL"
+ />
+
</configure>
+
+
Added: z3c.extfile/trunk/src/z3c/extfile/browser/views.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/browser/views.py (rev 0)
+++ z3c.extfile/trunk/src/z3c/extfile/browser/views.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -0,0 +1,51 @@
+from z3c.filetype import api
+from z3c.filetype.interfaces import filetypes
+from zope import interface
+from zope.cachedescriptors.property import Lazy
+import zope.datetime
+from zope.publisher.browser import BrowserView
+from zope.traversing.browser import absoluteurl
+from zope.app.component import hooks
+
+class ReadFileAbsoluteURL(absoluteurl.SiteAbsoluteURL):
+
+ def __str__(self):
+ siteURL = absoluteurl.absoluteURL(hooks.getSite(), self.request)
+ return '%s/++static++%s' % (siteURL, self.context.digest)
+
+ __call__ = __str__
+
+class ReadFileView(BrowserView):
+
+ @Lazy
+ def contentType(self):
+ ifaces = api.getInterfacesFor(self.context)
+ decl = interface.Declaration(ifaces)
+ for iface in decl.flattened():
+ mt = iface.queryTaggedValue(filetypes.MT)
+ if mt is not None:
+ return mt
+ raise ValueError, "Unable to detect content type %r" % self.context
+
+ def __call__(self):
+ """Sets various headers if the request for IFLVFile."""
+
+ self.request.response.setHeader('Content-Type',
+ self.contentType)
+ self.request.response.setHeader('Content-Length',
+ len(self.context))
+ ms= self.request.getHeader('If-Modified-Since', None)
+ if ms is not None:
+ # we cannot be modified, so we can return a 304 anytime
+ self.request.response.setStatus(304)
+ return ''
+ # set the modified header to creation time
+ self.request.response.setHeader(
+ 'Last-Modified',
+ zope.datetime.rfc1123_date(self.context.ctime))
+ # let it cache forever (360 days)
+ self.request.response.setHeader(
+ 'Cache-Control',
+ 's-max-age=31104000; max-age=31104000')
+ return self.context
+
Property changes on: z3c.extfile/trunk/src/z3c/extfile/browser/views.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: z3c.extfile/trunk/src/z3c/extfile/configure.zcml
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/configure.zcml 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/configure.zcml 2007-05-23 09:03:12 UTC (rev 75902)
@@ -1,35 +1,36 @@
<configure
- xmlns="http://namespaces.zope.org/zope"
- i18n_domain='zope'
- >
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain='zope'
+ >
- <adapter factory=".adapter.ReadFileResult" />
-
- <class class=".hashdir.ReadFile">
- <allow interface=".interfaces.IReadFile"/>
- </class>
-
- <class class=".hashdir.HashDir">
+ <adapter factory=".adapter.ReadFileResult" />
- <implements
- interface="zope.annotation.interfaces.IAttributeAnnotatable" />
+ <class class=".hashdir.ReadFile">
+ <allow interface=".interfaces.IReadFile"/>
+ </class>
- <require
- permission="zope.ManageServices"
- interface=".interfaces.IHashDir"
- />
+ <class class=".hashdir.HashDir">
- <require
- permission="zope.ManageServices"
- set_schema=".interfaces.IHashDir"
- />
+ <implements
+ interface="zope.annotation.interfaces.IAttributeAnnotatable" />
+ <require
+ permission="zope.ManageServices"
+ interface=".interfaces.IHashDir"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ set_schema=".interfaces.IHashDir"
+ />
+
</class>
<subscriber
- for="zope.app.appsetup.IDatabaseOpenedEvent"
- handler=".utility.bootStrapSubscriber"
- />
-
- <include package=".browser"/>
+ for="zope.app.appsetup.IDatabaseOpenedEvent"
+ handler=".utility.bootStrapSubscriber"
+ />
+
+ <include package=".browser"/>
+
</configure>
\ No newline at end of file
Added: z3c.extfile/trunk/src/z3c/extfile/datamanager.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/datamanager.py (rev 0)
+++ z3c.extfile/trunk/src/z3c/extfile/datamanager.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -0,0 +1,61 @@
+from zope import thread
+from zope import interface
+from zope import component
+from transaction.interfaces import IDataManager
+import interfaces
+import transaction
+
+_storage = thread.local()
+
+def getFile(digest):
+ if not hasattr(_storage, 'dataManager'):
+ _storage.dataManager = ReadFileDataManager()
+ txn = transaction.manager.get()
+ if txn is not None:
+ txn.join(_storage.dataManager)
+ return _storage.dataManager.getFile(digest)
+
+
+class ReadFileDataManager(object):
+
+ """Takes care of closing open files"""
+
+ interface.implements(IDataManager)
+
+ def __init__(self):
+ self.files = {}
+
+ @property
+ def hd(self):
+ return component.getUtility(interfaces.IHashDir)
+
+ def getFile(self, digest):
+ if digest in self.files:
+ return self.files[digest]
+ self.files[digest] = self.hd.open(digest)
+ return self.files[digest]
+
+ def _close(self):
+ for f in self.files.values():
+ f.close()
+
+ def abort(self, trans):
+ self._close()
+
+ def tpc_begin(self, trans):
+ pass
+
+ def commit(self, trans):
+ self._close()
+
+ def tpc_vote(self, trans):
+ pass
+
+ def tpc_finish(self, trans):
+ self._close()
+
+ def tpc_abort(self, trans):
+ self._close()
+
+ def sortKey(self):
+ return str(id(self))
Property changes on: z3c.extfile/trunk/src/z3c/extfile/datamanager.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: z3c.extfile/trunk/src/z3c/extfile/hashdir.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/hashdir.py 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/hashdir.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -7,8 +7,8 @@
import interfaces
from zope import interface
from persistent import Persistent
+from zope.cachedescriptors.property import Lazy
-
class HashDir(Persistent):
"""a directory holding files named after their sha1 hash"""
@@ -26,7 +26,7 @@
self.tmp = os.path.join(self.path, 'tmp')
self.var = os.path.join(self.path, 'var')
self._initPaths()
-
+
def _getPath(self):
return self._path
@@ -36,7 +36,7 @@
for path in [self.path,self.var,self.tmp]:
if not os.path.exists(path):
os.mkdir(path)
-
+
def new(self):
"""returns a new filehandle"""
handle, path = tempfile.mkstemp(prefix='dirty.',
@@ -53,7 +53,7 @@
else:
shutil.move(f.path, target)
return digest
-
+
def digests(self):
"""returns all digests stored"""
return os.listdir(self.var)
@@ -71,8 +71,8 @@
def open(self, digest):
return ReadFile(self.getPath(digest))
-
+
class ReadFile(object):
"""A lazy read file implementation"""
@@ -92,7 +92,15 @@
return self._v_file
self._v_file = file(self.name, 'rb', self.bufsize)
return self._v_file
-
+
+ @Lazy
+ def ctime(self):
+ return int(os.stat(self.name)[stat.ST_CTIME])
+
+ @Lazy
+ def atime(self):
+ return int(os.stat(self.name)[stat.ST_ATIME])
+
def __len__(self):
if self._v_len is None:
self._v_len = int(os.stat(self.name)[stat.ST_SIZE])
@@ -130,7 +138,7 @@
if not self.closed:
self._v_file.close()
self._v_file = None
-
+
def fileno(self):
return self._file.fileno()
Modified: z3c.extfile/trunk/src/z3c/extfile/hashdir.txt
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/hashdir.txt 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/hashdir.txt 2007-05-23 09:03:12 UTC (rev 75902)
@@ -68,6 +68,15 @@
>>> f.digest
'0db0e5fa1ecf3e7659504f2e4048434cd9f20d2d'
+We can get the creation- and access time of the file as timestamp. For
+testing we just look if it is smaller than now.
+
+ >>> import time
+ >>> f.ctime < time.time()
+ True
+ >>> f.atime >= f.ctime
+ True
+
The seek and tell operations are supported.
>>> f.seek(1)
Modified: z3c.extfile/trunk/src/z3c/extfile/interfaces.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/interfaces.py 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/interfaces.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -21,6 +21,8 @@
digest = schema.ASCII(title=u'Digest', readonly=True)
closed = schema.Bool(title=u'Closed', readonly=True)
+ ctime = schema.Float(title=u'Creation Time', readonly=True)
+ atime = schema.Float(title=u'Access Time', readonly=True)
def __len__():
"""returns the length/size of file"""
@@ -40,6 +42,8 @@
def __iter__():
"""see file.__iter__"""
+
+
class IWriteFile(IFile):
def write(s):
Added: z3c.extfile/trunk/src/z3c/extfile/namespace.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/namespace.py (rev 0)
+++ z3c.extfile/trunk/src/z3c/extfile/namespace.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -0,0 +1,15 @@
+from zope.traversing.namespace import view
+from zope.traversing.interfaces import TraversalError
+import datamanager
+
+class Static(view):
+
+ def traverse(self, name, ignored):
+ if len(name)==40:
+ try:
+ return datamanager.getFile(str(name))
+ except KeyError:
+ pass
+ raise TraversalError(self.context, name)
+
+
Property changes on: z3c.extfile/trunk/src/z3c/extfile/namespace.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3c.extfile/trunk/src/z3c/extfile/namespace.txt
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/namespace.txt (rev 0)
+++ z3c.extfile/trunk/src/z3c/extfile/namespace.txt 2007-05-23 09:03:12 UTC (rev 75902)
@@ -0,0 +1,112 @@
+=========================
+Static Readfile Namespace
+=========================
+
+The static namespace returns an IReadFiel object like the
+ExtBytesProperty does. The name is the digest of the file.
+
+Security Warning! Note that this namespace reveals content, that might
+be protected in other cases. It is not enabled by default. If you want
+to enable it include the 'namespace.zcml' file.
+
+At first we need some setup.
+
+ >>> from z3c.extfile import hashdir
+ >>> import tempfile, os
+ >>> tmp = tempfile.mkdtemp()
+ >>> hdPath = os.path.join(tmp, 'testhashdir')
+ >>> hd = hashdir.HashDir(hdPath)
+ >>> from zope import component
+ >>> from z3c.extfile.interfaces import IHashDir
+ >>> component.provideUtility(hd, provides=IHashDir)
+
+Let us make a test object whith an ExtBytesProperty in order to get
+some content into our hash directory.
+
+ >>> from z3c.extfile.property import ExtBytesProperty
+ >>> from cStringIO import StringIO
+ >>> class Foo(object):
+ ... data1 = ExtBytesProperty('data1')
+ ... data2 = ExtBytesProperty('data2')
+ >>> foo = Foo()
+ >>> foo.data1 = StringIO('contents of foo.data1')
+ >>> foo.data2 = StringIO('contents of foo.data2')
+
+Now our namespace should return a readfile.
+
+ >>> from z3c.extfile.namespace import Static
+ >>> ns = Static(None, None)
+
+A bad hash does not work:
+
+ >>> ns.traverse(u'not a hash', None)
+ Traceback (most recent call last):
+ ...
+ TraversalError: (None, u'not a hash')
+
+Existing hashes return a ReadFile.
+
+ >>> ns.traverse(foo.data1.digest, None)
+ <ReadFile named 'b34d7a2ebbef51196b8db24f6233e750d6a30e18'>
+ >>> ns.traverse(foo.data2.digest, None)
+ <ReadFile named '8923e22aa8772e3525dc91f008fc10ce6812b39d'>
+
+A non existing hash also raises a traversal error.
+
+ >>> ns.traverse('b34d7a2ebbef51196b8db24f6233e750d6a30e10', None)
+ Traceback (most recent call last):
+ ...
+ TraversalError: (None, 'b34d7a2ebbef51196b8db24f6233e750d6a30e10')
+
+Default Browserview
+===================
+
+When we use the namespace with a browser, we will get the default view
+implementation of Readfile.
+
+ >>> from z3c.extfile.browser import views
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> view = views.ReadFileView(foo.data1, request)
+
+The view returns the readfile directly, which is actually a file and
+will be directly and efficiently published by the publisher.
+
+ >>> view()
+ <ReadFile named 'b34d7a2ebbef51196b8db24f6233e750d6a30e18'>
+
+The view sets various headers. Note that the we can cache this view
+forever, so the cache time is set to 360 days. Also the
+"Last-Modified" header is set to the creation date of the file. The
+content type is guessed by using the z3c.filetype package, which uses
+magic numbers to get the type. For our sample content it can't find a
+type so it uses the fallback 'application/octet-stream'.
+
+ >>> request.response.getHeaders()
+ [('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)'),
+ ('Last-Modified', '...'), ('Content-Length', '21'),
+ ('Content-Type', 'application/octet-stream'),
+ ('Cache-Control', 's-max-age=31104000; max-age=31104000')]
+
+Absolute URLs
+=============
+
+With this namespace we now have an absolute url for each
+readfile. There is an absolute url view implemented for this.
+
+ >>> request = TestRequest()
+ >>> view = views.ReadFileAbsoluteURL(foo.data1, request)
+ >>> view()
+ 'http://127.0.0.1/++static++b34d7a2ebbef51196b8db24f6233e750d6a30e18'
+ >>> str(view)
+ 'http://127.0.0.1/++static++b34d7a2ebbef51196b8db24f6233e750d6a30e18'
+ >>> unicode(view)
+ u'http://127.0.0.1/++static++b34d7a2ebbef51196b8db24f6233e750d6a30e18'
+
+Cleanup
+
+ >>> import z3c.extfile.datamanager
+ >>> z3c.extfile.datamanager._storage.dataManager._close()
+ >>> import shutil
+ >>> shutil.rmtree(tmp)
+
Property changes on: z3c.extfile/trunk/src/z3c/extfile/namespace.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3c.extfile/trunk/src/z3c/extfile/namespace.zcml
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/namespace.zcml (rev 0)
+++ z3c.extfile/trunk/src/z3c/extfile/namespace.zcml 2007-05-23 09:03:12 UTC (rev 75902)
@@ -0,0 +1,21 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ i18n_domain='zope'
+ >
+
+ <view
+ name="static"
+ type="zope.interface.Interface"
+ provides="zope.traversing.interfaces.ITraversable"
+ for="zope.app.component.interfaces.ISite"
+ factory=".namespace.Static"
+ />
+
+ <adapter
+ name="static"
+ provides="zope.traversing.interfaces.ITraversable"
+ for="zope.app.component.interfaces.ISite"
+ factory=".namespace.Static"
+ />
+
+</configure>
\ No newline at end of file
Property changes on: z3c.extfile/trunk/src/z3c/extfile/namespace.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: z3c.extfile/trunk/src/z3c/extfile/property.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/property.py 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/property.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -1,27 +1,24 @@
-from zope import component, interface
+from zope import component
import interfaces
from cStringIO import StringIO
-from transaction.interfaces import IDataManager
-from zope import thread
-import transaction
+from datamanager import getFile, _storage
+
_marker = object()
+
BLOCK_SIZE = 1024*128
-_storage = thread.local()
-
class ExtBytesProperty(object):
"""a property which's values are stored as external files"""
def __init__(self, name):
self.__name = name
-
@property
def hd(self):
return component.getUtility(interfaces.IHashDir)
-
+
def __get__(self, inst, klass):
if inst is None:
@@ -29,12 +26,7 @@
digest = inst.__dict__.get(self.__name, _marker)
if digest is _marker:
return None
- if not hasattr(_storage, 'dataManager'):
- _storage.dataManager = ReadFileDataManager()
- txn = transaction.manager.get()
- if txn is not None:
- txn.join(_storage.dataManager)
- return _storage.dataManager.getFile(digest)
+ return getFile(digest)
def __set__(self, inst, value):
# ignore if value is None
@@ -66,46 +58,3 @@
f.write(chunk)
-class ReadFileDataManager(object):
-
- """Takes care of closing open files"""
-
- interface.implements(IDataManager)
-
- def __init__(self):
- self.files = {}
-
- @property
- def hd(self):
- return component.getUtility(interfaces.IHashDir)
-
- def getFile(self, digest):
- if digest in self.files:
- return self.files[digest]
- self.files[digest] = self.hd.open(digest)
- return self.files[digest]
-
- def _close(self):
- for f in self.files.values():
- f.close()
-
- def abort(self, trans):
- self._close()
-
- def tpc_begin(self, trans):
- pass
-
- def commit(self, trans):
- self._close()
-
- def tpc_vote(self, trans):
- pass
-
- def tpc_finish(self, trans):
- self._close()
-
- def tpc_abort(self, trans):
- self._close()
-
- def sortKey(self):
- return str(id(self))
Modified: z3c.extfile/trunk/src/z3c/extfile/tests.py
===================================================================
--- z3c.extfile/trunk/src/z3c/extfile/tests.py 2007-05-23 02:58:48 UTC (rev 75901)
+++ z3c.extfile/trunk/src/z3c/extfile/tests.py 2007-05-23 09:03:12 UTC (rev 75902)
@@ -1,6 +1,8 @@
import doctest
import unittest
from zope.testing.doctestunit import DocFileSuite, DocTestSuite
+from zope.app.testing import setup
+
def test_suite():
return unittest.TestSuite(
@@ -8,6 +10,11 @@
DocFileSuite('hashdir.txt',
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
),
+ DocFileSuite('namespace.txt',
+ setUp=setup.placefulSetUp,
+ tearDown=lambda x: setup.placefulTearDown(),
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
DocFileSuite('processor.txt',
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
),
More information about the Checkins
mailing list