Draw a line marking new stuff. #332
[zzz-spline-frontpage.git] / splinext / frontpage / controllers / frontpage.py
index 5aba8c5..a77edb2 100644 (file)
@@ -1,7 +1,5 @@
-from collections import defaultdict
 import datetime
 import logging
 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 import config, request, response, session, tmpl_context as c, url
 from pylons.controllers.util import abort, redirect_to
@@ -10,7 +8,8 @@ from sqlalchemy.orm.exc import NoResultFound
 
 from spline.lib import helpers as h
 from spline.lib.base import BaseController, render
 
 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__)
 
 
 log = logging.getLogger(__name__)
 
@@ -49,72 +48,56 @@ class FrontPageController(BaseController):
         Local plugins can override the fairly simple index.mako template to
         customize the front page layout.
         """
         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.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
 
                 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
+        last_seen_time = datetime.datetime.fromtimestamp(max(times))
+
+        if last_seen_time:
+            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)
+            meta.Session.commit()
+        else:
+            response.set_cookie('frontpage-last-seen-time', now)
+
+        # Done!  Feed to template
         c.updates = updates
 
         return render('/index.mako')
         c.updates = updates
 
         return render('/index.mako')