Stop invalidating the entire session before generating the page...
[zzz-spline-frontpage.git] / splinext / frontpage / controllers / frontpage.py
index 5aba8c5..900c87b 100644 (file)
@@ -1,16 +1,15 @@
-from collections import defaultdict
 import datetime
 import logging
-import re
 
 from pylons import config, request, response, session, tmpl_context as c, url
-from pylons.controllers.util import abort, redirect_to
+from pylons.controllers.util import abort, redirect
 from routes import request_config
 from sqlalchemy.orm.exc import NoResultFound
 
 from spline.lib import helpers as h
 from spline.lib.base import BaseController, render
-from spline.lib.plugin.load import run_hooks
+from spline.model import meta
+from splinext.frontpage.sources import max_age_to_datetime
 
 log = logging.getLogger(__name__)
 
@@ -49,72 +48,61 @@ class FrontPageController(BaseController):
         Local plugins can override the fairly simple index.mako template to
         customize the front page layout.
         """
-        # XXX no reason to do this on the fly; cache it on server startup
-        update_config = defaultdict(dict)  # source_name => config
-        key_rx = re.compile(
-            '(?x) ^ spline-frontpage [.] sources [.] (\w+) (?: [.] (\w+) )? $')
-        for key, val in config.iteritems():
-            match = key_rx.match(key)
-            if not match:
-                continue
-
-            source_name, subkey = match.groups()
-            if not subkey:
-                # This is the type declaration; use a special key
-                subkey = '__type__'
-
-            if subkey in ('limit', 'max_age'):
-                val = int(val)
-            update_config[source_name][subkey] = val
-
-        global_limit = int(config.get('spline-frontpage.limit', 10))
-        now = datetime.datetime.now()
-        try:
-            global_max_age = now - datetime.timedelta(
-                seconds=int(config['spline-frontpage.max_age']))
-        except KeyError:
-            global_max_age = None
-
-        # Ask plugins to deal with this stuff for us!
-        updates = []
-        for source, source_config in update_config.iteritems():
-            hook_name = 'frontpage_updates_' + source_config['__type__']
-
-            # Merge with the global config
-            merged_config = source_config.copy()
-            del merged_config['__type__']
 
-            merged_config['limit'] = min(
-                merged_config.get('limit', global_limit),
-                global_limit,
-            )
-
-            try:
-                local_max_age = now - datetime.timedelta(
-                    seconds=merged_config['max_age'])
-            except KeyError:
-                local_max_age = None
-
-            if global_max_age and local_max_age:
-                merged_config['max_age'] = max(global_max_age, local_max_age)
-            else:
-                merged_config['max_age'] = global_max_age or local_max_age
-
-            # Hooks should return a list of FrontPageUpdate-like objects,
-            # making this return value a list of lists
-            updates_lol = run_hooks(hook_name, **merged_config)
-            updates += sum(updates_lol, [])
-
-            # Little optimization: maximum age effectively becomes the age of
-            # the oldest thing that would still appear on the page, as anything
-            # older would drop off the end no matter what.
-            # So sort by descending time and crop each iteration...
+        updates = []
+        global_limit = config['spline-frontpage.limit']
+        global_max_age = max_age_to_datetime(
+            config['spline-frontpage.max_age'])
+
+        c.sources = config['spline-frontpage.sources']
+        for source in c.sources:
+            new_updates = source.poll(global_limit, global_max_age)
+            updates.extend(new_updates)
+
+            # Little optimization: once there are global_limit items, anything
+            # older than the oldest cannot possibly make it onto the list.  So,
+            # bump global_max_age to that oldest time if this is ever the case.
             updates.sort(key=lambda obj: obj.time, reverse=True)
-            updates = updates[:global_limit]
+            del updates[global_limit:]
 
-            if updates:
+            if updates and len(updates) == global_limit:
                 global_max_age = updates[-1].time
 
+        # Find the oldest unseen item, to draw a divider after it.
+        # If this stays as None, the divider goes at the top
+        c.last_seen_item = None
+        # Could have a timestamp in the stash if this is a user, or in a cookie
+        # if this session has ever been logged out...
+        times = []
+        for source in (c.user.stash, request.cookies):
+            try:
+                times.append( int(source['frontpage-last-seen-time']) )
+            except (KeyError, ValueError):
+                pass
+
+        if times:
+            last_seen_time = datetime.datetime.fromtimestamp(max(times))
+            for update in updates:
+                if update.time > last_seen_time:
+                    c.last_seen_item = update
+                else:
+                    break
+
+        # Save ~now~ as the last-seen time
+        now = datetime.datetime.now().strftime('%s')
+        if c.user:
+            c.user.stash['frontpage-last-seen-time'] = now
+            meta.Session.add(c.user)
+        else:
+            response.set_cookie('frontpage-last-seen-time', now)
+
+        # Done!  Feed to template
         c.updates = updates
 
-        return render('/index.mako')
+        ret = render('/index.mako')
+
+        # Commit AFTER rendering the template!  Committing invalidates
+        # everything in the session, undoing any eagerloading that may have
+        # been done by sources
+        meta.Session.commit()
+        return ret