# HG changeset patch
# User Paul Boddie <paul@boddie.org.uk>
# Date 1390585938 -3600
# Node ID 71d9815d2e6f5487ec87c4f5c04e5a156ffd21d3
# Parent  5738b78f53b540ece0b48efa49b63fa4cbc09be9
Added caching support for the "pageparams" dependency where the request
parameters are combined with the page name to make a cache entry.

diff -r 5738b78f53b5 -r 71d9815d2e6f MoinMoin/Page.py
--- a/MoinMoin/Page.py	Sat Nov 30 20:09:43 2013 +0100
+++ b/MoinMoin/Page.py	Fri Jan 24 18:52:18 2014 +0100
@@ -41,6 +41,7 @@
 
 from MoinMoin import config, caching, user, util, wikiutil
 from MoinMoin.logfile import eventlog
+from MoinMoin.support.python_compatibility import hash_new
 
 def is_cache_exception(e):
     args = e.args
@@ -1351,7 +1352,7 @@
 
     def loadCache(self, request):
         """ Return page content cache or raises 'CacheNeedsUpdate' """
-        cache = caching.CacheEntry(request, self, self.getFormatterName(), scope='item')
+        cache = caching.CacheEntry(request, self, self.getCacheKey(request), scope='item')
         attachmentsPath = self.getPagePath('attachments', check_create=0)
         if cache.needsUpdate(self._text_filename(), attachmentsPath):
             raise Exception('CacheNeedsUpdate')
@@ -1372,7 +1373,7 @@
         """ Format content into code, update cache and return code """
         import marshal
         from MoinMoin.formatter.text_python import Formatter
-        formatter = Formatter(request, ["page"], self.formatter)
+        formatter = Formatter(request, ["page", "pageparams"], self.formatter)
 
         # Save request state while formatting page
         saved_current_lang = request.current_lang
@@ -1384,10 +1385,39 @@
         src = formatter.assemble_code(text)
         code = compile(src.encode(config.charset),
                        self.page_name.encode(config.charset), 'exec')
-        cache = caching.CacheEntry(request, self, self.getFormatterName(), scope='item')
+        self.enforceCacheLimit(request)
+        cache = caching.CacheEntry(request, self, self.getCacheKey(request), scope='item')
         cache.update(marshal.dumps(code))
         return code
 
+    def enforceCacheLimit(self, request):
+        """ Prevent too many cache entries being stored for a page """
+        keys = caching.get_cache_list(request, self, 'item')
+        try:
+            cache_limit = int(getattr(request.cfg, 'page_cache_limit', "10"))
+        except ValueError:
+            cache_limit = 10
+
+        if len(keys) >= cache_limit:
+            items = [caching.CacheEntry(request, self, key, scope='item') for key in keys]
+            item_ages = [(item.mtime(), item) for item in items]
+            item_ages.sort()
+            for item_age, item in item_ages[:-cache_limit]:
+                item.remove()
+
+    def getCacheKey(self, request):
+        """ Generate a cache key for a page using optional request information """
+        key = self.getFormatterName()
+        if request.args:
+            args = request.args.items()
+            args.sort()
+            key_args = []
+            for k, v in args:
+                key_args.append("%s=%s" % (k, wikiutil.url_quote(v)))
+            arg_str = "&".join(key_args)
+            key = "%s:%s" % (key, hash_new('sha1', arg_str).hexdigest())
+        return key
+
     def _specialPageText(self, request, special_type):
         """ Output the default page content for new pages.
 
# HG changeset patch
# User Paul Boddie <paul@boddie.org.uk>
# Date 1390599896 -3600
# Node ID e3850612c891d5b6468f212acd797413c70fe18f
# Parent  71d9815d2e6f5487ec87c4f5c04e5a156ffd21d3
Acquire the request formatter name when the page formatter has not yet been set.

diff -r 71d9815d2e6f -r e3850612c891 MoinMoin/Page.py
--- a/MoinMoin/Page.py	Fri Jan 24 18:52:18 2014 +0100
+++ b/MoinMoin/Page.py	Fri Jan 24 22:44:56 2014 +0100
@@ -1255,15 +1255,17 @@
             request.formatter = old_formatter
 
 
-    def getFormatterName(self):
+    def getFormatterName(self, request=None):
         """ Return a formatter name as used in the caching system
 
+        @param request: the active request (optional)
         @rtype: string
         @return: formatter name as used in caching
         """
-        if not hasattr(self, 'formatter') or self.formatter is None:
+        formatter = getattr(self, 'formatter', None) or request and getattr(request, 'formatter', None)
+        if not formatter:
             return ''
-        module = self.formatter.__module__
+        module = formatter.__module__
         return module[module.rfind('.') + 1:]
 
     def canUseCache(self, parser=None):
@@ -1407,7 +1409,7 @@
 
     def getCacheKey(self, request):
         """ Generate a cache key for a page using optional request information """
-        key = self.getFormatterName()
+        key = self.getFormatterName(request)
         if request.args:
             args = request.args.items()
             args.sort()
# HG changeset patch
# User Paul Boddie <paul@boddie.org.uk>
# Date 1390675730 -3600
# Node ID 908ceaf41dbe9f664f0c0813abc7b4c0611022fe
# Parent  e3850612c891d5b6468f212acd797413c70fe18f
Added tracking of reported dependencies and prevented generation of specific
cache entries for request parameter combinations when no extension requires or
supports such specific entries.

diff -r e3850612c891 -r 908ceaf41dbe MoinMoin/Page.py
--- a/MoinMoin/Page.py	Fri Jan 24 22:44:56 2014 +0100
+++ b/MoinMoin/Page.py	Sat Jan 25 19:48:50 2014 +0100
@@ -1354,9 +1354,12 @@
 
     def loadCache(self, request):
         """ Return page content cache or raises 'CacheNeedsUpdate' """
-        cache = caching.CacheEntry(request, self, self.getCacheKey(request), scope='item')
-        attachmentsPath = self.getPagePath('attachments', check_create=0)
-        if cache.needsUpdate(self._text_filename(), attachmentsPath):
+        for with_params in (True, False):
+            cache = caching.CacheEntry(request, self, self.getCacheKey(request, with_params), scope='item')
+            attachmentsPath = self.getPagePath('attachments', check_create=0)
+            if not cache.needsUpdate(self._text_filename(), attachmentsPath):
+                break
+        else:
             raise Exception('CacheNeedsUpdate')
 
         import marshal
@@ -1388,7 +1391,10 @@
         code = compile(src.encode(config.charset),
                        self.page_name.encode(config.charset), 'exec')
         self.enforceCacheLimit(request)
-        cache = caching.CacheEntry(request, self, self.getCacheKey(request), scope='item')
+        # Determine whether the parameters/args need to be incorporated into the
+        # cache entry key.
+        with_params = "pageparams" in formatter.getReportedDependencies()
+        cache = caching.CacheEntry(request, self, self.getCacheKey(request, with_params), scope='item')
         cache.update(marshal.dumps(code))
         return code
 
@@ -1407,10 +1413,10 @@
             for item_age, item in item_ages[:-cache_limit]:
                 item.remove()
 
-    def getCacheKey(self, request):
+    def getCacheKey(self, request, with_params=False):
         """ Generate a cache key for a page using optional request information """
         key = self.getFormatterName(request)
-        if request.args:
+        if with_params and request.args:
             args = request.args.items()
             args.sort()
             key_args = []
diff -r e3850612c891 -r 908ceaf41dbe MoinMoin/formatter/text_python.py
--- a/MoinMoin/formatter/text_python.py	Fri Jan 24 22:44:56 2014 +0100
+++ b/MoinMoin/formatter/text_python.py	Sat Jan 25 19:48:50 2014 +0100
@@ -10,6 +10,7 @@
 
 import time
 from MoinMoin import wikiutil
+from MoinMoin.support.python_compatibility import set
 
 
 class Formatter:
@@ -40,6 +41,12 @@
         self.text_cmd_begin = '\nrequest.write('
         self.text_cmd_end = ')\n'
 
+        # Record dependency requirements of certain content
+        self.__dependencies = set()
+
+    def getReportedDependencies(self):
+        return self.__dependencies
+
     def assemble_code(self, text):
         """inserts the code into the generated text
         """
@@ -186,7 +193,10 @@
             return self.formatter.div(on, **kw)
 
     def macro(self, macro_obj, name, args, markup=None):
-        if self.__is_static(macro_obj.get_dependencies(name)):
+        Dependencies = macro_obj.get_dependencies(name)
+        self.__dependencies.update(Dependencies)
+
+        if self.__is_static(Dependencies):
             # XXX: why is this necessary??
             macro_obj.formatter = self
             return macro_obj.execute(name, args)
@@ -204,6 +214,7 @@
             Dependencies = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", parser_name, "Dependencies")
         except (wikiutil.PluginMissingError, wikiutil.PluginAttributeError):
             Dependencies = self.defaultDependencies
+        self.__dependencies.update(Dependencies)
 
         if self.__is_static(Dependencies):
             return self.formatter.parser(parser_name, lines)
