-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__)
Hook handlers should return a list of FrontPageUpdate objects.
- Standard hook parameters are `limit`, the maximum number of items that
- should ever be returned.
+ Standard hook parameters are:
+ `limit`, the maximum number of items that should ever be returned.
+ `max_age`, the number of seconds after which items expire.
+ `title`, a name for the source.
+ `icon`, an icon to show next to its name.
+
+ `limit` and `max_age` are also global options.
Updates are configured in the .ini like so:
run_hooks('frontpage_updates_updatetype', opt1=val1, opt2=val2)
- Standard options are not shown and take precedence over whatever's in
- the config file.
-
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__'
-
- update_config[source_name][subkey] = val
-
-
- global_config = dict(
- limit = 10,
- )
- # Ask plugins to deal with this stuff for us!
updates = []
- for source, source_config in update_config.iteritems():
- source_config2 = source_config.copy()
- hook_name = 'frontpage_updates_' + source_config2.pop('__type__')
- source_config2.update(global_config)
-
- # Hooks should return a list of FrontPageUpdate objects, making this
- # return value a list of lists
- updates_lol = run_hooks(hook_name, **source_config2)
- updates += sum(updates_lol, [])
-
- # Sort everything by descending time, then crop to the right number of
- # items
- updates.sort(key=lambda obj: obj.time)
- updates.reverse()
- c.updates = updates[:global_config['limit']]
-
- return render('/index.mako')
+ 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)
+ del updates[global_limit:]
+
+ 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
+
+ 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