[Checkins] SVN: lovely.responsecache/trunk/ - added a utility to purge cache entries. a page to manually purge cache

Bernd Roessl bernd.roessl at lovelysystems.com
Sat Jan 19 17:13:22 EST 2008


Log message for revision 82957:
  - added a utility to purge cache entries. a page to manually purge cache
    entries is also included
  
  

Changed:
  U   lovely.responsecache/trunk/CHANGES.txt
  U   lovely.responsecache/trunk/setup.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/BROWSER.txt
  A   lovely.responsecache/trunk/src/lovely/responsecache/PURGE.txt
  A   lovely.responsecache/trunk/src/lovely/responsecache/PURGEVIEW.txt
  U   lovely.responsecache/trunk/src/lovely/responsecache/configure.zcml
  U   lovely.responsecache/trunk/src/lovely/responsecache/ftesting.zcml
  U   lovely.responsecache/trunk/src/lovely/responsecache/interfaces.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/meta.zcml
  A   lovely.responsecache/trunk/src/lovely/responsecache/purge.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/tests.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/view.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/zcml.py
  U   lovely.responsecache/trunk/src/lovely/responsecache/zcml.txt

-=-
Modified: lovely.responsecache/trunk/CHANGES.txt
===================================================================
--- lovely.responsecache/trunk/CHANGES.txt	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/CHANGES.txt	2008-01-19 22:13:22 UTC (rev 82957)
@@ -2,6 +2,12 @@
 Changes for lovely.responsecache
 ================================
 
+2008/01/19 0.4.0
+================
+
+- added a utility to purge cache entries. a page to manually purge cache
+  entries is also included
+
 2007/12/04 0.3.0a3
 ==================
 

Modified: lovely.responsecache/trunk/setup.py
===================================================================
--- lovely.responsecache/trunk/setup.py	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/setup.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -23,7 +23,7 @@
 
 setup(
     name = 'lovely.responsecache',
-    version = '0.3.0a3',
+    version = '0.4.0',
     author = "Lovely Systems",
     author_email = "office at lovelysystems.com",
     description = "Cache results of ContentProviders",
@@ -36,10 +36,12 @@
     package_dir = {'':'src'},
     namespace_packages = ['lovely',],
     install_requires = ['setuptools',
+                        'pycurl',
                         'lovely.memcached',
                         'zope.app.publication',
                         'zope.app.security',
                         'zope.contentprovider',
+                        'zope.formlib',
                         'zope.publisher',
                         'zope.schema',
                         'zope.security',

Modified: lovely.responsecache/trunk/src/lovely/responsecache/BROWSER.txt
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/BROWSER.txt	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/BROWSER.txt	2008-01-19 22:13:22 UTC (rev 82957)
@@ -75,7 +75,7 @@
   /++ckey++authenticated/test.html
   /++ckey++authenticated/test.html/IContent
   /++ckey++authenticated/test.html/MyViewlet
-  
+
 We can also cascade such keys.
 
   >>> browser.open('http://localhost/++ckey++anonymous/'
@@ -84,9 +84,10 @@
   /++ckey++anonymous/++ckey++aSessionId/test.html
   /++ckey++anonymous/++ckey++aSessionId/test.html/IContent
   /++ckey++anonymous/++ckey++aSessionId/test.html/MyViewlet
-  
+
   >>> browser.open('http://localhost/++ckey++anonymous/'
   ...              '++ckey++aSessionId/test.html')
   >>> print browser.headers.get('x-memcached-hit')
   /++ckey++anonymous/++ckey++aSessionId/test.html
-  
+
+

Added: lovely.responsecache/trunk/src/lovely/responsecache/PURGE.txt
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/PURGE.txt	                        (rev 0)
+++ lovely.responsecache/trunk/src/lovely/responsecache/PURGE.txt	2008-01-19 22:13:22 UTC (rev 82957)
@@ -0,0 +1,205 @@
+=========================
+Purging an external cache
+=========================
+
+ - multiple caches
+ - zmi UI to manually purge
+ - implemented as utility
+ - configured by zcml
+
+
+Let's create a purge utility. To ::
+
+    >>> from zope import component
+    >>> from lovely.responsecache.purge import PurgeUtil
+    >>> HTTP_PORT = 33334
+    >>> hosts = ['http://localhost:%d' % HTTP_PORT]
+    >>> util = PurgeUtil(hosts, timeout=1, retryDelay=0)
+    >>> component.provideUtility(util)
+
+Let's purge an expression::
+
+    >>> util.purge('http://domain/purge_expression1')
+
+Now call the endOfRequest function. NOTE: In wild this function is registed
+as an event handler and will be called at the end of any request::
+
+    >>> from lovely.responsecache.purge import endOfRequest
+    >>> log_info.clear()
+    >>> endOfRequest(None)
+    >>> print log_info
+    lovely.responsecache.purge ERROR
+      unable to purge 'http://localhost:33334/purge_expression1', reason: (7, "couldn't connect to host")
+
+As we can see we could not reach any server. So let's set up a http server::
+
+    >>> import threading
+    >>> from BaseHTTPServer import HTTPServer
+    >>> from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+    >>> class StoppableHttpServer(HTTPServer):
+    ...     """http server that reacts to self.stop flag"""
+    ...     def serve_forever (self):
+    ...         """Handle one request at a time until stopped."""
+    ...         self.stop = False
+    ...         while not self.stop:
+    ...             self.handle_request()
+
+    >>> purgedUrls = []
+    >>> class StoppableHttpRequestHandler(SimpleHTTPRequestHandler):
+    ...     """http request handler with QUIT stopping the server"""
+    ...     def do_QUIT (self):
+    ...         """send 200 OK response, and set server.stop to True"""
+    ...         self.send_response(200)
+    ...         self.end_headers()
+    ...         self.server.stop = True
+    ...     def do_PURGE (self):
+    ...         """log the purge"""
+    ...         purgedUrls.append(self.path)
+    ...         self.send_response(200)
+    ...         self.end_headers()
+
+    >>> def startServer():
+    ...     address = ("localhost", HTTP_PORT)
+    ...     server = StoppableHttpServer(address, StoppableHttpRequestHandler)
+    ...     server.serve_forever()
+
+    >>> serverThread = threading.Thread(target=startServer)
+    >>> serverThread.start()
+
+OK, next try to purge some cache entries::
+
+    >>> log_info.clear()
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> print log_info
+
+    >>> purgedUrls
+    ['/purge_expression1']
+
+If the endOfRequest get called twice there should not be more to purge::
+
+    >>> purgedUrls = []
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    []
+
+Now call purge with more than one expressions::
+
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> util.purge('http://domain/purge_expression2')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1', '/purge_expression2']
+
+Now call purge with duplicated expressions::
+
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> util.purge('http://domain/purge_expression2')
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1', '/purge_expression2']
+
+If there are multiple hosts to purge it has to work this way::
+
+    >>> HTTP_PORT2 = 33335
+    >>> hosts = ['http://localhost:%d' % HTTP_PORT,
+    ...          'http://localhost:%d' % HTTP_PORT2]
+    >>> util.hosts = hosts
+
+    >>> def startServer2():
+    ...     address = ("localhost", HTTP_PORT2)
+    ...     server = StoppableHttpServer(address, StoppableHttpRequestHandler)
+    ...     server.serve_forever()
+
+    >>> serverThread2 = threading.Thread(target=startServer2)
+    >>> serverThread2.start()
+
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1', '/purge_expression1']
+
+If a server is not reachable it should be ignored for the configured
+retryDelay. Currently the retryDelay is set to zero so we have to set
+a proper one::
+
+    >>> util.retryDelay = 2
+
+    >>> from httplib import HTTPConnection
+    >>> conn = HTTPConnection("localhost:%d" % HTTP_PORT)
+    >>> conn.request("QUIT", "/")
+    >>> ret = conn.getresponse()
+
+    >>> log_info.clear()
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1']
+
+As we can see there is just one purge in the list. So we have to checkout
+the logging info::
+
+    >>> print log_info
+    lovely.responsecache.purge ERROR
+      unable to purge 'http://localhost:33334/purge_expression1', reason: (7, "couldn't connect to host")
+
+The failed host is listed in the dict failedHosts::
+
+    >>> util.failedHosts
+    {'http://localhost:33334': ...}
+
+If we purge once again the host on the failedHosts should not get purged::
+
+    >>> log_info.clear()
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1']
+
+    >>> print log_info
+
+If the host is up again it will be ignored till the rertyDelay elapsed::
+
+    >>> serverThread = threading.Thread(target=startServer)
+    >>> serverThread.start()
+
+    >>> log_info.clear()
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1']
+
+    >>> print log_info
+
+Now lets wait until the retryDelay was elapsed and than the host will
+get purged again::
+
+    >>> from time import sleep
+    >>> sleep(2)
+
+    >>> log_info.clear()
+    >>> purgedUrls = []
+    >>> util.purge('http://domain/purge_expression1')
+    >>> endOfRequest(None)
+    >>> purgedUrls
+    ['/purge_expression1', '/purge_expression1']
+
+Stopping the http servers::
+
+    >>> from httplib import HTTPConnection
+    >>> conn = HTTPConnection("localhost:%d" % HTTP_PORT)
+    >>> conn.request("QUIT", "/")
+    >>> ret = conn.getresponse()
+
+    >>> from httplib import HTTPConnection
+    >>> conn = HTTPConnection("localhost:%d" % HTTP_PORT2)
+    >>> conn.request("QUIT", "/")
+    >>> ret = conn.getresponse()


Property changes on: lovely.responsecache/trunk/src/lovely/responsecache/PURGE.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: lovely.responsecache/trunk/src/lovely/responsecache/PURGEVIEW.txt
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/PURGEVIEW.txt	                        (rev 0)
+++ lovely.responsecache/trunk/src/lovely/responsecache/PURGEVIEW.txt	2008-01-19 22:13:22 UTC (rev 82957)
@@ -0,0 +1,43 @@
+The purge view
+==============
+
+This page is to manually purge cache entries.
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization','Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+The page is available on the root of the zmi if a purge utility was configured::
+
+  >>> browser.open('http://localhost/@@contents.html')
+  >>> browser.getLink('purge').click()
+  Traceback (most recent call last):
+  ...
+  LinkNotFoundError
+
+So let's configure a utility and call the page again. Now the menu item
+will be available::
+
+  >>> from zope import component
+  >>> from lovely.responsecache.purge import PurgeUtil
+  >>> HTTP_PORT = 33333
+  >>> hosts = ['http://localhost:%d' % HTTP_PORT]
+  >>> util = PurgeUtil(hosts, timeout=1, retryDelay=0)
+  >>> component.provideUtility(util)
+
+  >>> browser.open('http://localhost/@@contents.html')
+  >>> browser.getLink('purge').click()
+
+The expression is a required field::
+
+  >>> browser.getControl(name="form.actions.purge").click()
+  >>> 'There were errors' in browser.contents
+  True
+
+If the expression was entered the caches get purged::
+
+  >>> browser.getControl(name="form.expression").value = ".*js"
+  >>> browser.getControl(name="form.actions.purge").click()
+  >>> 'There were errors' in browser.contents
+  False


Property changes on: lovely.responsecache/trunk/src/lovely/responsecache/PURGEVIEW.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: lovely.responsecache/trunk/src/lovely/responsecache/configure.zcml
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/configure.zcml	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/configure.zcml	2008-01-19 22:13:22 UTC (rev 82957)
@@ -11,6 +11,8 @@
 
   <subscriber handler=".event.setCache"/>
   <subscriber handler=".event.setAuthInfoCookie"/>
+  <subscriber for="zope.app.publication.interfaces.IEndRequestEvent"
+              handler=".purge.endOfRequest"/>
 
   <view
       name="ckey" type="*"
@@ -18,4 +20,21 @@
       factory=".namespace.ckey"
       />
 
+  <browser:page
+      name="purge.html"
+      for="zope.app.folder.interfaces.IRootFolder"
+      permission="zope.ManageContent"
+      class=".view.PurgeView"
+      />
+
+  <browser:menuItem
+      title="purge"
+      for="zope.app.folder.interfaces.IRootFolder"
+      menu="zmi_views"
+      action="purge.html"
+      permission="zope.ManageContent"
+      order="2000"
+      filter="python:modules['lovely.responsecache.view'].canPurge(context)"
+      />
+
 </configure>

Modified: lovely.responsecache/trunk/src/lovely/responsecache/ftesting.zcml
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/ftesting.zcml	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/ftesting.zcml	2008-01-19 22:13:22 UTC (rev 82957)
@@ -4,7 +4,7 @@
            xmlns:meta="http://namespaces.zope.org/meta"
            i18n_domain="zope">
 
-  
+
   <include package="zope.app.securitypolicy" file="meta.zcml" />
 
   <include
@@ -17,7 +17,8 @@
       />
 
   <include package="zope.viewlet" file="meta.zcml" />
-  
+  <include package="lovely.responsecache" file="meta.zcml" />
+
   <include package="zope.app.authentication" />
   <securityPolicy
       component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy" />
@@ -29,49 +30,49 @@
 
   <role id="zope.Manager" title="Site Manager" />
 
-  
+
   <principal
       id="zope.manager"
       title="Administrator"
       login="mgr"
       password="mgrpw" />
-  
+
   <grant
       role="zope.Manager"
       principal="zope.manager"
       />
-  
+
   <unauthenticatedPrincipal
       id="zope.anybody"
       title="Unauthenticated User" />
 
   <unauthenticatedGroup
       id="zope.Anybody"
-      title="Unauthenticated Users" 
+      title="Unauthenticated Users"
       />
-  
 
+
   <authenticatedGroup
       id="zope.Authenticated"
-      title="Authenticated Users" 
+      title="Authenticated Users"
       />
-  
+
   <everybodyGroup
       id="zope.Everybody"
-      title="All Users" 
+      title="All Users"
       />
 
   <include package="zope.contentprovider"/>
+  <include package="zope.formlib"/>
   <include package="zope.viewlet"/>
   <include package="lovely.memcached" />
   <include package="lovely.responsecache" />
   <include package="lovely.responsecache.testing" />
 
-
   <grant permission="zope.View"
          principal="zope.Everybody"/>
-      
-  
+
+
   <grantAll role="zope.Manager" />
-  
+
 </configure>

Modified: lovely.responsecache/trunk/src/lovely/responsecache/interfaces.py
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/interfaces.py	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/interfaces.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -35,3 +35,16 @@
                                required=False,
                                default=[])
 
+
+class IPurge(interface.Interface):
+
+    def purge(expr, escapes='+-'):
+        """Method to purge the hosts with the given expression"""
+
+
+class IPurgeView(interface.Interface):
+    """Schema for the purge view"""
+
+    expression = schema.TextLine(title=u'expression',
+                                 description=u'Expression to purge',
+                                 required=True)

Modified: lovely.responsecache/trunk/src/lovely/responsecache/meta.zcml
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/meta.zcml	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/meta.zcml	2008-01-19 22:13:22 UTC (rev 82957)
@@ -10,6 +10,12 @@
         handler=".zcml.cacheSettingsDirective"
         />
 
+    <meta:directive
+        name="purge"
+        schema=".zcml.IPurgeDirective"
+        handler=".zcml.purgeDirective"
+        />
+
   </meta:directives>
 
 </configure>

Added: lovely.responsecache/trunk/src/lovely/responsecache/purge.py
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/purge.py	                        (rev 0)
+++ lovely.responsecache/trunk/src/lovely/responsecache/purge.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import logging
+import threading
+from time import time
+import urlparse
+
+from zope import component, interface
+from zope.schema.fieldproperty import FieldProperty
+
+import interfaces
+
+
+storage = threading.local()
+EXPRS_ATTR='varnish_purgeurls'
+
+log = logging.getLogger(__name__)
+
+class PurgeUtil(object):
+    """Utilty to purge mutliple caches"""
+    interface.implements(interfaces.IPurge)
+
+    failedHosts = {}
+
+    def __init__(self, hosts, timeout, retryDelay):
+        self.hosts = hosts
+        self.timeout = timeout
+        self.retryDelay = retryDelay
+
+    def purge(self, expr, escapes='+-'):
+        for esc in escapes:
+            expr = expr.replace(esc, '\\' + esc)
+        if not hasattr(storage, EXPRS_ATTR):
+            setattr(storage, EXPRS_ATTR, set([expr]))
+        else:
+            getattr(storage, EXPRS_ATTR).add(expr)
+
+    def doPurge(self):
+        exprs = getattr(storage, EXPRS_ATTR, None)
+        if exprs is None:
+            return
+        for host in self.hosts:
+            if host in self.failedHosts.keys():
+                if self.failedHosts[host]  + self.retryDelay > time():
+                    continue
+                else:
+                    del self.failedHosts[host]
+            def urls(exprs):
+                for expr in exprs:
+                    url = self._expr2URL(expr)
+                    yield urlparse.urlunparse(urlparse.urlparse(host)[:2] + url)
+            if not self._purgeURLs(urls(exprs)):
+                self.failedHosts[host] = time()
+        delattr(storage, EXPRS_ATTR)
+
+    def _expr2URL(self, expr):
+        #URL: scheme://netloc/path;parameters?query#fragment
+        expr = str(expr)
+        if expr.startswith('http://'):
+            parts = urlparse.urlparse(expr)
+            parts = parts[2:]
+        else:
+            parts = (expr, '', '', '')
+        return parts
+
+    def _purgeURLs(self, urls):
+        import pycurl
+        try:
+            c = pycurl.Curl()
+            c.setopt(c.WRITEFUNCTION, self.ignoreWrite)
+            c.setopt(c.CUSTOMREQUEST,'PURGE')
+            c.setopt(c.TIMEOUT, self.timeout)
+            for url in urls:
+                c.setopt(c.URL, url)
+                c.perform()
+            c.close()
+            return True
+        except Exception, e:
+            log.error('unable to purge %r, reason: %s' % (url, e))
+            return False
+
+    def ignoreWrite(data):
+        pass
+
+def endOfRequest(event):
+    utils = component.getAllUtilitiesRegisteredFor(interfaces.IPurge)
+    for util in utils:
+        util.doPurge()
+
+


Property changes on: lovely.responsecache/trunk/src/lovely/responsecache/purge.py
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: lovely.responsecache/trunk/src/lovely/responsecache/tests.py
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/tests.py	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/tests.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -29,8 +29,11 @@
 from z3c.testing import layer
 from lovely.memcached.interfaces import IMemcachedClient
 
+from zope.testing.loggingsupport import InstalledHandler
+
 from view import ResponseCacheSettings
 
+
 class IMyView(interface.Interface):
     pass
 
@@ -54,11 +57,18 @@
     test.globs['root'] = root
     test.globs['IMyView'] = IMyView
 
+    log_info = InstalledHandler('lovely.responsecache.purge')
+    test.globs['log_info'] = log_info
+
+
 def tearDown(test):
     setup.placefulTearDown()
 
 
 def test_suite():
+    fsuite = functional.FunctionalDocFileSuite('PURGEVIEW.txt')
+    fsuite.layer=ResponseCacheLayer
+
     level1Suites = (
         DocFileSuite(
             'zcml.txt', setUp=setUp, tearDown=tearDown,
@@ -68,7 +78,13 @@
             'credentials.txt', setUp=setUp, tearDown=tearDown,
             optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
             ),
+        DocFileSuite(
+            'PURGE.txt', setUp=setUp, tearDown=tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+            ),
+        fsuite,
         )
+
     fsuite = functional.FunctionalDocFileSuite('BROWSER.txt')
     fsuite.layer=ResponseCacheLayer
     level2Suites = (

Modified: lovely.responsecache/trunk/src/lovely/responsecache/view.py
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/view.py	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/view.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -16,15 +16,19 @@
 """
 __docformat__ = "reStructuredText"
 
-from interfaces import IResponseCacheSettings
+import re
 from zope import interface
 from zope import component
+from zope.formlib import form
 from zope.schema.fieldproperty import FieldProperty
-from lovely.memcached.interfaces import IMemcachedClient
 from zope.traversing.browser.absoluteurl import absoluteURL
+from zope.publisher.browser import BrowserPage
 from zope.app.component.hooks import getSite
-import re
 
+from lovely.memcached.interfaces import IMemcachedClient
+from interfaces import IResponseCacheSettings, IPurge, IPurgeView
+
+
 CKEY_PAT = pat = re.compile(r'\+\+ckey\+\+([^\/]*)\/')
 
 class ResponseCacheSettings(object):
@@ -62,3 +66,16 @@
                                       name=self.cacheName,
                                       context=self.context)
 
+
+class PurgeView(form.AddForm):
+
+    form_fields = form.FormFields(IPurgeView)
+
+    @form.action(u'Purge')
+    def handle_purge_action(self, action, data):
+        util = component.getUtility(IPurge)
+        util.purge(data['expression'])
+
+
+def canPurge(context):
+    return component.queryUtility(IPurge) is not None

Modified: lovely.responsecache/trunk/src/lovely/responsecache/zcml.py
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/zcml.py	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/zcml.py	2008-01-19 22:13:22 UTC (rev 82957)
@@ -27,9 +27,11 @@
 from zope.proxy import removeAllProxies
 from zope.configuration.fields import GlobalObject, Tokens
 from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.security.zcml import Permission
 
-from interfaces import IResponseCacheSettings
+from interfaces import IResponseCacheSettings, IPurge
 from view import ResponseCacheSettings
+from purge import PurgeUtil
 
 from zope.i18nmessageid import MessageFactory
 _ = MessageFactory('lovely.responseheader')
@@ -193,3 +195,46 @@
                 _context.info),
         )
 
+
+
+class IPurgeDirective(interface.Interface):
+    """Parameters for the purge directive."""
+
+    hosts = Tokens(
+        title=u'Hosts',
+        description=u'Lists of hosts to get purged',
+        required=True,
+        value_type=schema.URI(title=u"host"))
+
+    timeout = schema.Int(
+        title = _(u'Timeout'),
+        description=u'Timeout for purge requests in seconds. Keep it short!',
+        required=True,
+        default=1,
+        )
+
+    retryDelay = schema.Int(
+        title = _(u'Cachename'),
+        description=u'Retry delay to purge after a timeout in seconds.',
+        required=True,
+        default=60,
+        )
+
+    permission = Permission(
+        title=_("Permission"),
+        description=_("Permission required to use this component."),
+        required=False,
+        )
+
+
+def purgeDirective(_context, hosts, timeout, retryDelay, permission=None):
+    """Function to create a perge utility"""
+
+    util = PurgeUtil(hosts, timeout, retryDelay)
+    zcml.utility(_context,
+                 provides=IPurge,
+                 component=util,
+                 permission=permission,
+                 name='')
+
+

Modified: lovely.responsecache/trunk/src/lovely/responsecache/zcml.txt
===================================================================
--- lovely.responsecache/trunk/src/lovely/responsecache/zcml.txt	2008-01-19 20:33:47 UTC (rev 82956)
+++ lovely.responsecache/trunk/src/lovely/responsecache/zcml.txt	2008-01-19 22:13:22 UTC (rev 82957)
@@ -216,3 +216,24 @@
    >>> settings.someOtherVariable
    u'B'
 
+Purge
+=====
+
+Configure a purge utility and check if it was created::
+
+  >>> xmlconfig(StringIO(baseTemplate% (
+  ...      """
+  ...      <purge
+  ...        hosts="http://localhost http://otherhost"
+  ...        />
+  ...      """
+  ...     )))
+
+  >>> from lovely.responsecache.interfaces import IPurge
+  >>> purger = component.getUtility(IPurge)
+  >>> purger
+  <lovely.responsecache.purge.PurgeUtil object at ...>
+
+  >>> purger.hosts
+  ['http://localhost', 'http://otherhost']
+



More information about the Checkins mailing list